bigywater.tech
MySQL / InnoDB · 18

自增主键:回滚之后为什么不会复用编号

插入3行后开事务插入5行再ROLLBACK,看AUTO_INCREMENT计数器是不是也跟着回滚——验证发现,下一条插入直接跳到了id=9,中间的编号永久消失。

4→9
回滚后下一次插入跳过的编号范围
5个编号
永久消失、不会被复用的空洞大小
计数器非事务性
AUTO_INCREMENT不受ROLLBACK影响的设计

做的事:插入3行,开事务再插5行,然后回滚

先插入3行确认基线,再在一个事务里插入5行并观察AUTO_INCREMENT,最后回滚。

操作CREATE TABLE t5 (id INT PRIMARY KEY AUTO_INCREMENT, v INT); INSERT INTO t5 (v) VALUES (1),(2),(3); 查AUTO_INCREMENT → 4(下一次插入该用的id) START TRANSACTION; INSERT INTO t5 (v) VALUES (100),(101),(102),(103),(104); -- 占用id 4~8 查AUTO_INCREMENT(事务内)→ 4(information_schema缓存值,事务内还没刷新) ROLLBACK;

验证:回滚之后,数据没了,但编号呢

ROLLBACK之后,先确认数据真的回滚了,再看真正的关键问题——下一次插入用几号。

结果ROLLBACK之后 SELECT * FROM t5 → 只剩3行:(1,1) (2,2) (3,3) ← 数据确实回滚了 再插入一行 INSERT INTO t5 (v) VALUES (999) → 结果:(1,1) (2,2) (3,3) (9,999) ← 新行的id是9,不是4!

数据本身确实回滚干净了——事务里那5行(100~104)彻底消失,表里只剩最初的3行。但下一次插入拿到的id是9,中间4、5、6、7、8这5个编号永久消失了,再也不会被用到。

这就是自增主键的行为:AUTO_INCREMENT计数器不是事务的一部分,它不会跟着ROLLBACK一起回滚。计数器在执行INSERT语句、拿到新id的那一刻就已经往前走了,不管这条INSERT所在的事务最后是提交还是回滚。这也是为什么"自增主键会有空洞"——回滚、批量插入失败重试、甚至只是插入语句本身没有完成,都可能留下永久的编号空洞。

为什么InnoDB要这样设计,不能等提交了再分配id吗

如果自增id要等事务提交才分配,会有什么问题。

如果两个并发事务都在等"提交时才分配id",就必须让它们按提交顺序排队分配,这意味着自增id的分配会变成一个的串行瓶颈——后提交的事务必须等前一个事务提交完,才能知道自己该用哪个id。InnoDB选择的是"插入语句执行时就立刻分配id",这样多个并发事务可以各自独立地拿号、各自插入,互不阻塞;代价就是一旦某个事务回滚,它已经拿到手的那些编号就浪费掉了,不会被回收再利用。这是用"编号可能留空洞"换"并发插入不用排队"的权衡。

分步演示:自增主键回滚后留下的编号空洞

每一步都是本文上面执行SQL得到的原始结果。

回滚清空了数据,但没有清空自增计数器
这一步新验证的状态

    本文用到的工具

    • 本机 MySQL 26.7.0(InnoDB),AUTO_INCREMENT主键
    • information_schema.TABLES:查询AUTO_INCREMENT当前值
    京ICP备2026067502号-1京公网安备11010502063487号