事务回滚后自增ID不回收是InnoDB主动设计:为提升并发性能,ID在INSERT执行初期即预分配,早于事务提交判断;回滚仅撤销数据,不退还已分配ID,故必然跳号。

事务回滚后自增ID不回收是InnoDB的主动设计
不是bug,也不是配置错误,而是InnoDB为并发性能做的取舍:自增值在INSERT语句执行初期就分配,早于事务是否提交的判断。回滚只撤销行数据,不撤销已分配的ID。
常见错误现象:Duplicate entry报错后ID跳变、手动ROLLBACK后下一条成功插入的ID比预期大1或更多。
- 执行
BEGIN; INSERT INTO t (name) VALUES ('x'); ROLLBACK;→ 计数器仍+1 -
INSERT IGNORE INTO t (email) VALUES ('exists@example.com');→ 唯一键冲突,ID照常消耗 -
INSERT ... ON DUPLICATE KEY UPDATE同样触发预分配,失败也不退还ID
innodb_autoinc_lock_mode如何影响跳号幅度
这个参数不决定“是否跳号”,但显著影响“跳多少”和“何时跳”。MySQL 8.0默认值1(连续模式)已在大多数场景平衡了性能与可控性。
-
innodb_autoinc_lock_mode = 0:锁表分配,每次只拿1个ID,跳号最小但并发差 -
innodb_autoinc_lock_mode = 1(默认):简单INSERT用mutex,INSERT SELECT等批量操作会预分配一段(如+8),跳号更明显 -
innodb_autoinc_lock_mode = 2(交错模式):高并发下预分配范围更大,跳号剧烈,且要求binlog_format = ROW,否则主从可能不一致
查当前值:SELECT @@innodb_autoinc_lock_mode;;临时调整会话级可用SET SESSION innodb_autoinc_lock_mode = 0;,但无法禁用跳号本身。
哪些操作看似“没写入”,实则已消耗ID
只要MySQL引擎判定“这一条INSERT可能需要新ID”,就会走分配流程。成败与否,不影响ID已被占用的事实。
-
INSERT IGNORE遇到唯一键冲突 → 分配ID,但行未落盘 -
INSERT INTO t SELECT ...子查询返回空集 → InnoDB仍可能按预估批量分配ID -
REPLACE INTO→ 先删旧行,再插新行,新行用下一个自增ID,不是复用原ID - 显式插入
NULL或省略自增列 → 必然触发分配;显式指定正整数(如INSERT INTO t (id, name) VALUES (100, 'x'))则完全绕过自增机制
MySQL 8.0+重启后还跳号吗?
不会“重置”,但依然会“跳”。8.0+把AUTO_INCREMENT值持久化到redo log和数据字典,重启后继续从上次断点递增,不再像5.7那样靠MAX(id)+1重建。
这意味着:一次INSERT失败导致计数器升到1000,重启后仍是1000,下一条成功插入就是1000——它不“找回”跳掉的号,只是不再“额外乱跳”。
真正容易被忽略的是:空洞本身无害,危险在于把它当连续序号用。比如用WHERE id BETWEEN 100 AND 200做分页、导出或幂等校验,一旦中间有空洞,逻辑就漏数据。别试图用ALTER TABLE ... AUTO_INCREMENT=1修复,那会破坏外键引用、主从同步和应用缓存。











