mysql自增主键不连续是innodb为保障并发安全与性能主动设计的正常行为,事务回滚、唯一键冲突、批量插入及重启(5.7及以前)均会导致跳号,属确定性机制而非异常。

MySQL中自增主键不连续不是异常,而是InnoDB为并发安全和性能主动做出的设计选择;只要没手动乱设AUTO_INCREMENT值或误配innodb_autoinc_lock_mode,所有“跳号”都符合预期。
事务回滚后ID仍递增是确定性行为
InnoDB在INSERT语句解析阶段就向计数器申请下一个ID,远早于事务是否提交或回滚的判断。这意味着:
- 执行
BEGIN; INSERT INTO t VALUES (NULL, 'x'); ROLLBACK;后,AUTO_INCREMENT值已+1,无法撤销 - 该机制避免了高并发下为回收ID而加重锁或做唯一性校验,否则吞吐会断崖式下跌
-
SHOW CREATE TABLE t里显示的AUTO_INCREMENT=xxx就是当前真实计数器值,可直接观察变化
唯一键冲突也会消耗ID
哪怕插入失败,只要SQL中用了NULL或省略主键字段,InnoDB就会走自增流程:
- 流程固定:取当前自增值 → 改写SQL → 校验唯一约束 → 失败丢弃行,但ID不回收
- 例如表当前
AUTO_INCREMENT=100,执行INSERT INTO t VALUES (NULL, 'dup@email.com')报Duplicate entry,计数器仍升至101 -
INSERT IGNORE、ON DUPLICATE KEY UPDATE、REPLACE INTO全部触发相同逻辑,无法规避
批量插入(INSERT SELECT)跳号最猛
默认innodb_autoinc_lock_mode = 1下,InnoDB为减少锁竞争,会对批量语句预分配多段ID:
- 执行
INSERT INTO t SELECT NULL, a, b FROM src LIMIT 5,可能直接从100跳到108 - 设为2(交错模式)能缓解但不消除,且要求
binlog_format = ROW - 设为0(传统全表锁)可保连续,但并发插入几乎阻塞,线上严禁使用
- 真正可控的方式只有放弃批量语句,改用单条
INSERT循环,或由应用层生成ID(如雪花ID)
重启后AUTO_INCREMENT变小只发生在5.7及更早版本
MySQL 8.0+已将自增值持久化进redo log和数据字典,崩溃/重启后不会丢失:
- 5.7及以前:重启后执行
SELECT MAX(id) + 1重建,若曾手动插过大ID又删掉,就会“突降” - 8.0+:
SHOW CREATE TABLE t里的AUTO_INCREMENT=xxx就是持久化值,重启不变 -
ALTER TABLE t AUTO_INCREMENT = N在8.0+会被真正持久化,但若N ,后续插入直接报<code>ERROR 1062,隐患隐蔽
空洞本身无害,真正危险的是业务逻辑假设ID连续——比如用WHERE id BETWEEN 100 AND 200做分页,或把ID当订单号对外展示。这类依赖必须重构,没有绕过机制的捷径。











