主键约束
本教程共 46 篇 · 第 31 篇 · 更新于 2026-07-30 · 约 9 分钟阅读
31. 主键约束
本节目标:搞清楚主键(Primary Key)是什么、怎么建、怎么选,理解自增主键的工作机制,学会用复合主键应对多列联合唯一场景。
31.1 主键是什么
主键(Primary Key) 是表中用来唯一标识每一行数据的列(或列组合)。它有两个硬性要求:
- 唯一:每一行的主键值不能重复;
- 非空:主键列不允许为 NULL。
说白了,主键就是一张表的”身份证号”。有了它,你就能精确定位到任意一行数据。
-- 创建带主键的 users 表
CREATE TABLE users (
id INT PRIMARY KEY,
username VARCHAR(50) NOT NULL,
email VARCHAR(100),
age INT,
created_at DATETIME DEFAULT CURRENT_TIMESTAMP
) ENGINE=InnoDB DEFAULT CHARSET=utf8mb4;
id INT PRIMARY KEY 这一行做了两件事:把 id 设为主键,同时隐含了 NOT NULL 约束。
Note在 InnoDB 存储引擎中,主键不只是约束,它还决定了数据的物理存储顺序。InnoDB 会按主键值排序把数据存在 B+ 树的叶子节点上,这就是聚簇索引(Clustered Index)。主键选得好不好,直接影响查询性能。
31.2 单列主键
最常见的主键形式——用一列当主键。上面的例子就是单列主键。
插入数据时,主键值不能重复:
INSERT INTO users(id, username, email) VALUES(1, 'zhangsan', 'zhangsan@test.com');
-- 再插一条 id=1 的会报错
INSERT INTO users(id, username, email) VALUES(1, 'lisi', 'lisi@test.com');
-- ERROR 1062 (23000): Duplicate entry '1' for key 'PRIMARY'
主键值也不能为 NULL:
INSERT INTO users(username, email) VALUES('wangwu', 'wangwu@test.com');
-- 如果 id 没有默认值,会报错
31.3 自增主键
手动指定主键值太麻烦,尤其是数据量大的时候。MySQL 提供了 AUTO_INCREMENT 属性,让主键自动增长。
CREATE TABLE users (
id INT AUTO_INCREMENT PRIMARY KEY,
username VARCHAR(50) NOT NULL,
email VARCHAR(100),
age INT,
created_at DATETIME DEFAULT CURRENT_TIMESTAMP
) ENGINE=InnoDB DEFAULT CHARSET=utf8mb4;
插入数据时不指定 id,MySQL 自动分配:
INSERT INTO users(username, email, age) VALUES('zhangsan', 'zhangsan@test.com', 25);
INSERT INTO users(username, email, age) VALUES('lisi', 'lisi@test.com', 30);
SELECT id, username FROM users;
-- 结果:
-- 1 | zhangsan
-- 2 | lisi
自增值的几个特点
- 自增值从 1 开始(默认),每次加 1;
- 如果删除了某行,那个自增值不会回收(InnoDB 引擎下);
- 可以用
AUTO_INCREMENT = N指定起始值。
-- 让自增从 1000 开始
CREATE TABLE users (
id INT AUTO_INCREMENT PRIMARY KEY,
username VARCHAR(50)
) AUTO_INCREMENT = 1000;
-- 或者修改已有表的自增起始值
ALTER TABLE users AUTO_INCREMENT = 1000;
Tip实际项目中,自增主键的起始值设大一点(比如 10000),留出前面几百个号给特殊数据用,是个常见的做法。
查看当前自增值
SHOW TABLE STATUS LIKE 'users'\G
-- Auto_increment 字段就是下一个自增值
也可以查 information_schema:
SELECT AUTO_INCREMENT FROM information_schema.TABLES
WHERE TABLE_SCHEMA = '你的库名' AND TABLE_NAME = 'users';
WarningInnoDB 的自增值在 8.0 之前是持久化到内存里的,重启后可能重置为
MAX(id)+1。MySQL 8.0 起改为持久化到 redo log,重启后自增值不会回退。本教程基线版本 26.7.0 已经是持久化机制,不用担心这个问题。
31.4 复合主键
有时候一列没法唯一标识一行,需要多列组合当主键,这就是复合主键(Composite Primary Key)。
比如订单明细表,一个订单有多件商品,用 order_id + product_id 组合才能唯一标识一行:
CREATE TABLE order_items (
order_id INT NOT NULL,
product_id INT NOT NULL,
quantity INT NOT NULL DEFAULT 1,
price DECIMAL(10,2) NOT NULL,
PRIMARY KEY (order_id, product_id)
) ENGINE=InnoDB DEFAULT CHARSET=utf8mb4;
复合主键要求:组合值唯一,但单独某一列可以重复。
INSERT INTO order_items(order_id, product_id, quantity, price) VALUES(1, 101, 2, 9.90);
INSERT INTO order_items(order_id, product_id, quantity, price) VALUES(1, 102, 1, 19.90); -- order_id 重复,但组合不重复,OK
INSERT INTO order_items(order_id, product_id, quantity, price) VALUES(1, 101, 3, 9.90);
-- ERROR 1062: Duplicate entry '1-101' for key 'PRIMARY'
Note复合主键的列顺序很重要。
(order_id, product_id)和(product_id, order_id)在约束效果上是一样的(都要求组合唯一),但在 InnoDB 聚簇索引的排序和查询效率上有区别。把区分度高的列放前面通常更好。
31.5 建表后添加和删除主键
表已经建好了,后面想加主键也行:
-- 前提:要设为主键的列不能有 NULL 值和重复值
ALTER TABLE users ADD PRIMARY KEY (id);
删除主键:
ALTER TABLE users DROP PRIMARY KEY;
Warning如果主键列上有
AUTO_INCREMENT属性,必须先去掉自增属性才能删除主键,否则会报错。实际开发中,主键一旦定好就别轻易改。
31.6 主键选取原则
主键不是随便选的,选错了后面性能和维护都受影响。几条经验法则:
1. 优先用自增整数列
InnoDB 的聚簇索引按主键排序存储数据。自增主键每次插入的值都是递增的,新数据直接追加到 B+ 树末尾,页分裂少,写入效率高。
如果用 UUID 这类无序字符串当主键,每次插入位置随机,会导致频繁的页分裂和数据移动,性能明显下降。
| 主键类型 | 插入性能 | 存储空间 | 排序连续性 |
|---|---|---|---|
| 自增 INT | 高 | 4 字节 | 连续 |
| 自增 BIGINT | 高 | 8 字节 | 连续 |
| UUID | 低 | 16 字节 | 随机 |
| 业务字段(如手机号) | 中 | 不固定 | 不连续 |
2. 主键要稳定不变
主键一旦确定就不应该频繁修改。InnoDB 中改主键值意味着数据要在 B+ 树里搬家,代价很大。别用可能变动的业务字段(如手机号、邮箱)当主键。
3. 主键尽量短
InnoDB 的所有二级索引都存储了主键值。主键越长,每个二级索引节点能放的数据越少,索引树越高,查询越慢。INT(4 字节)或 BIGINT(8 字节)是最佳选择。
4. 不要用业务字段当主键
业务规则是会变的。比如手机号可能升级、身份证号可能输错需要改。用自增 ID 做主键,业务字段做唯一索引,是更安全的方案。
Tip我之前踩过这个坑——早期项目用邮箱当主键,后来用户要改邮箱,连带所有关联表的外键都要改,改到崩溃。从那以后我一律用自增 ID 做主键,业务唯一性用 UNIQUE 约束保证。
31.7 小结
- 主键保证唯一和非空,是定位数据的”身份证”;
AUTO_INCREMENT让主键自动增长,InnoDB 8.0+ 持久化自增值;- 复合主键用多列组合,组合值唯一即可,单列可重复;
- 主键选取原则:自增整数、稳定不变、尽量短、不用业务字段;
- InnoDB 中主键即聚簇索引,直接影响数据存储和查询性能。
下一节讲外键约束,看两张表怎么通过主键关联起来。