mysql自增id本质是不重复分配器,非序列号;事务回滚、唯一键冲突或插入失败均不回退自增值,因分配在写入前完成,且为保障并发性能与一致性而刻意设计。

MySQL 的自增 ID 本质不是“序列号”,而是“不重复分配器”——只要不重复,就满足设计目标。连续性从来不是它的承诺,所以看到 AUTO_INCREMENT=100 却表里只有 95 行,完全正常。
事务回滚后自增ID为什么不会回退
InnoDB 在执行 INSERT 时,先向自增计数器申请下一个值(比如从 98 → 99),再尝试写入行;一旦申请成功,这个值就“已消耗”,哪怕后续因唯一键冲突、主键重复或 ROLLBACK 导致插入失败,计数器也不会倒带。
典型复现步骤:
SET autocommit = 0; BEGIN; INSERT INTO t VALUES (NULL, 1, 1); -- 假设当前 AUTO_INCREMENT=5,此语句拿到 id=5 并尝试插入 ROLLBACK; SHOW CREATE TABLE t\G -- 输出中 AUTO_INCREMENT=6,不是 5
关键点:
-
innodb_autoinc_lock_mode不影响该行为:无论设为 0/1/2,自增值分配都在语句执行前完成 - 这是有意设计,避免高并发下反复检查是否已存在某 ID,牺牲连续性换性能与一致性
- 无法通过任何会话级配置关闭该行为
DELETE 后新插入的ID为什么不重用空缺
删除操作只移除数据行,不触发自增计数器重算。MySQL 不扫描全表找“最小空洞”,因为这在大表上代价太高,且违背 WAL(Write-Ahead Logging)和 MVCC 的设计哲学。
例如:
INSERT INTO t VALUES (NULL, 1, 1), (NULL, 2, 2), (NULL, 3, 3); -- id=1,2,3 DELETE FROM t WHERE id = 2; INSERT INTO t VALUES (NULL, 4, 4); -- 实际得到 id=4,不是 2
注意:
- 即使重启 MySQL 5.7 实例,它也只会基于
MAX(id)重新设为MAX(id)+1,仍不会填空(如删了 2,MAX=3,重启后仍是 4) - MySQL 8.0 持久化了自增值,但持久化的值仍是“上次分配过的最大值+1”,不是“可用最小正整数”
-
TRUNCATE TABLE是例外:它重置计数器为 1(或auto_increment_offset),但这是 DDL,非 DML
哪些操作真正会改变 AUTO_INCREMENT 值
只有显式修改或引擎层重载才会动这个值,日常 DML 几乎都不碰它:
-
ALTER TABLE t AUTO_INCREMENT = N:强制设为 ≥ 当前最大 id+1 的整数,否则会被忽略 -
TRUNCATE TABLE t:清空并重置为起始值(通常 1) - MySQL 5.7 及更早版本重启后首次访问表:执行
SELECT MAX(id) FROM t并设为该值+1 - MySQL 8.0+ 正常运行中:自增值变更写入 redo log,崩溃恢复后可还原,但依然不“填洞”
最易被忽略的一点:SHOW CREATE TABLE 显示的 AUTO_INCREMENT=N 是“下一个待分配值”,不是“当前最大值”。它可能比实际最大 id 大很多,也可能(极少见)因重启略小——但永远不等于“最小未使用 ID”。











