MySQL自增主键不连续是InnoDB为保障高并发性能与事务隔离性所做的确定性设计,非bug;事务回滚、唯一键冲突、批量插入均会预分配ID且不可回收,8.0+通过redo log持久化确保重启不丢失自增值。

不是事务回滚“导致”跳跃严重,而是它和批量插入、唯一键冲突一起构成跳号的常规组合;单独一次回滚只让计数器+1,真正造成“跳几百”的,几乎全是 INSERT SELECT 或 LOAD DATA INFILE 这类批量操作。
事务回滚只会让AUTO_INCREMENT+1,不会“跳一大段”
回滚本身对自增计数器的影响非常确定:只要执行了带 NULL 或省略自增列的 INSERT,哪怕立刻 ROLLBACK,计数器也只推进1位。
- 现象示例:
BEGIN; INSERT INTO t (name) VALUES ('x'); ROLLBACK;→ 下次成功插入拿到的ID比之前大1,不多不少 - 原因:InnoDB在语句解析阶段就向计数器申请下一个值,这个动作不依赖事务最终状态
- 注意:
SELECT @@auto_increment_offset和SELECT @@auto_increment_increment不影响单次回滚行为,只改变步长和起始偏移
真正让ID“跳几百”的是批量插入预分配
当你看到ID从100直接跳到200,大概率是碰上了 innodb_autoinc_lock_mode = 1(MySQL 8.0默认)下的批量预分配逻辑。
安装、修复、更新或打包一个基于 Python 和 PyMySQL 实现的 Windows `mysql` 命令。适用于用户希望在 PowerShell/CMD 中输入 `mysql` 时重定向到 Python 实现、需要交互式输入 MySQL 连接信息、需要同步到当前用户本地 bin 目录,或需要将该能力封装成可公开复用的 Codex skill。
-
INSERT INTO t SELECT NULL, a, b FROM src LIMIT 10可能一次性预留16个ID,哪怕只成功插入7行,计数器也升到116 -
LOAD DATA INFILE同样触发大段预分配,且无法通过会话级设置绕过 - 设为
innodb_autoinc_lock_mode = 0可强制每次只拿1个ID,但会锁整张表,线上基本不可用 - 查当前模式:
SELECT @@innodb_autoinc_lock_mode;
唯一键冲突也会悄无声息地消耗ID
很多人以为只有“写入成功”才占ID,其实只要引擎判定“这一条INSERT需要新ID”,就会立即分配——失败也不退还。
-
INSERT IGNORE INTO t (email) VALUES ('exists@example.com');→ 报错或静默丢弃,但ID已+1 -
INSERT INTO t (name, email) VALUES ('x', 'exists@example.com');→ 报Duplicate entry 'xxx' for key 'email',ID照常递增 -
REPLACE INTO先删后插,新行用下一个ID,不是复用原ID - 反复触发冲突时,
AUTO_INCREMENT值会持续上涨,中间空洞不可逆
重启不会修复空洞,但8.0+不再“额外乱跳”
MySQL 8.0+把自增值持久化到redo log和数据字典,重启后继续从上次断点递增。这意味着:
- 你不会看到重启后ID从1开始,或因重建MAX(id)+1而意外重复
- 但也不会“自动填补”之前跳过的号,比如计数器停在1000,重启后下一条仍是1000
-
ALTER TABLE t AUTO_INCREMENT = N无法倒拨:若N小于当前最大id,MySQL仅发warning,不生效 - 真正容易被忽略的是:空洞一旦产生,就永远存在;业务层不应依赖ID连续性做分页、范围判断或导出序号

















