MySQL 自增主键是如何工作的?会不会连续?
简化版
MySQL 自增主键由 AUTO_INCREMENT 生成递增值,适合 InnoDB 聚簇索引写入;但自增值不保证连续,事务回滚、插入失败、批量插入、重启和并发都可能造成空洞。
详细版
自增主键常见定义:
id BIGINT PRIMARY KEY AUTO_INCREMENT
插入时不指定 id,MySQL 会为新行分配下一个自增值。它的好处是短小、递增、写入友好,适合 InnoDB 聚簇索引。
但自增 id 不等于连续编号。比如插入时分配了 id=10,随后事务回滚,10 可能不会再被复用;插入失败或并发批量插入也可能消耗自增值。因此不能用自增 id 判断业务连续性,也不应把它当作订单号、票据号这类必须连续的编号。
完整版教学
一、自增主键解决什么问题
自增主键的目标是给每行生成一个简单唯一的数字标识。
CREATE TABLE users (
id BIGINT PRIMARY KEY AUTO_INCREMENT,
name VARCHAR(50)
);
INSERT INTO users(name) VALUES ('Alice'), ('Bob');
如果当前从 1 开始,可能得到:
id | name
1 | Alice
2 | Bob
它让应用不用手动生成单库唯一 id,也让 InnoDB 聚簇索引插入更接近顺序追加。
记忆钩子:自增 id 保证“递增生成”,不保证“无空洞连续”。
二、为什么自增主键适合 InnoDB
InnoDB 按主键组织聚簇索引。自增主键新值通常比旧值大,新记录更容易插入 B+ 树右侧。
已有 id:1,2,3,4
新插入 id=5 -> 追加到右侧
新插入 id=6 -> 继续追加
和随机 UUID 相比,自增主键更短、更有序,页分裂和随机写入压力通常更小。
这也是很多业务表默认使用 BIGINT AUTO_INCREMENT 的原因:简单、稳定、性能友好。
三、自增值为什么不保证连续
自增值分配后,可能因为事务回滚或插入失败而产生空洞。
示例:
START TRANSACTION;
INSERT INTO users(name) VALUES ('Tom'); -- 分配 id=10
ROLLBACK;
INSERT INTO users(name) VALUES ('Jerry'); -- 可能得到 id=11
结果中没有 id=10,这很正常。数据库更关心并发安全和生成效率,而不是让数字看起来连续。
可能造成空洞的场景:
| 场景 | 是否可能产生空洞 |
|---|---|
| 事务回滚 | 可能 |
| 唯一键冲突插入失败 | 可能 |
| 批量插入预分配 | 可能 |
| 删除历史数据 | 一定会出现缺口 |
四、自增锁和并发插入
自增值生成需要并发控制,避免两个事务拿到同一个 id。InnoDB 有自增锁相关机制,不同配置和版本下锁策略有所差异。
直观理解:
事务 A 申请自增值 -> 得到 100
事务 B 申请自增值 -> 得到 101
对于简单插入,数据库可以较高效地分配 id;对于批量插入或 INSERT ... SELECT,可能需要预估或锁定更长时间。
面试里不必强背每种 lock mode 的细节,但要知道:自增主键在高并发写入下有专门机制保证唯一分配,同时可能出现空洞。
五、为什么不能把自增 id 当业务编号
很多业务编号要求连续、不可猜、带日期或满足审计规则,自增主键并不适合直接承担这些职责。
例如订单号如果直接使用自增 id:
10001, 10002, 10005
用户可能看到订单量,竞争对手也可能推测业务规模;空洞还可能让财务或运营误以为丢单。
更稳的做法是:主键用自增 id,业务订单号单独生成并加唯一索引。
id BIGINT PRIMARY KEY AUTO_INCREMENT,
order_no VARCHAR(64) NOT NULL,
UNIQUE KEY uk_order_no(order_no)
六、分布式场景下的限制
单库自增 id 简单好用,但分库分表后会遇到全局唯一和趋势递增问题。
常见处理方式:
不同库设置不同步长:库1 生成 1,3,5;库2 生成 2,4,6
号段模式:从中心服务批量取一段 id
雪花算法:按时间戳 + 机器号 + 序列生成趋势递增 id
如果系统明确会分库分表,提前规划 id 生成方案比后期迁移主键更轻松。
自增主键适合单库或单写入口,分布式扩展时要重新评估。
七、常见误区与追问
- 误区:自增主键一定连续。 回滚、失败、删除、批量插入都可能产生空洞。
- 误区:自增 id 可以直接当订单号。 它可预测且不保证连续,通常应和业务编号分离。
- 误区:手动插入 id 永远没影响。 手动插入较大 id 可能推进后续自增值。
- 误区:自增主键只影响唯一性。 在 InnoDB 中它还影响聚簇索引写入顺序和页分裂。
- 追问:删除最大 id 后下次会复用吗? 通常不要依赖复用行为,应把自增值看作只增不保证填洞。
- 追问:分库分表还能用自增吗? 可以通过步长、号段或全局 id 服务处理,但单库自增不再天然全局唯一。
八、加强记忆
自增主键回答要同时说好处和边界:好处是短小递增、单库唯一、InnoDB 写入友好;边界是不保证连续、不适合业务编号、分布式要重新设计。看到“会不会连续”这个追问,答案一定是否定的。