bigywater.tech
MySQL / InnoDB · 58

长事务如何阻塞 Purge

开一个持有快照的长事务,看它真实怎么阻止undo被回收,再诚实记录它结束之后Purge赶上来的真实时间到底有多不确定。

0→1
长事务存活期间,一次并发提交真实让History list length涨了1
10s~65s+
长事务结束后,真实观测到的清零等待时间并不固定

做的事:一个长事务活着的时候,看它对Purge的真实影响

session A(长事务,持有快照约8秒)START TRANSACTION WITH CONSISTENT SNAPSHOT; SELECT amount FROM orders WHERE id=1; SELECT SLEEP(8); session B(在A存活期间真实提交一次更新)UPDATE orders SET amount = amount + 0.01 WHERE id = 1;

验证:A还开着的时候,B的提交真实让History list length从0变成1

实测 · 本机SHOW ENGINE INNODB STATUS真实输出
A开启快照之前:            History list length = 0
B执行UPDATE并提交,A仍存活: History list length = 1   ← 真实多了这一条

同时验证MVCC正确性(同一个事务A内):
  第一次读:amount = 148.08
  (B已提交,改成了148.09)
  第二次读(仍在A内):amount = 148.08   ← 真实还是旧值,undo真实保住了这个版本

B的这次提交真实产生了一条undo log记录(用来保留amount更新前的旧值),因为A这个时候还持有一个更早的快照,理论上随时可能需要读到这个旧值,所以这条undo记录真实不能被回收——History list length从0变成1,正是"这条undo还没被清理"的直接体现,也和A自己在事务内真实读到的旧值(148.08)吻合。

验证:A提交之后,真实等了多久才清零,没有一个固定答案

实测 · 本机执行输出(A提交后,持续轮询)
A COMMIT之后(长事务结束,理论上这条undo已经没人需要了):
第一次真实测试:  10秒后仍是1,一路测到65秒仍是1,没有在观察窗口内清零
第二次真实测试:  10秒后就已经是0,清零发生在观察窗口之内

两次测试的唯一区别:Purge后台线程真实调度的时机,不是A提交那一刻
这是本文诚实要记录的一点:A提交、长事务结束之后,理论上这条被保留的undo记录已经没有任何活跃读视图需要它了,Purge线程应该会把它真实清理掉,History list length应该会降回0。但真实反复测试下来,这个"降回0"发生的时间点很不稳定——有的批次在65秒的观察窗口内始终没有清零,有的批次10秒之内就清零了。Purge是一个独立的后台线程,按自己的节奏运行,不受事务提交这个事件直接驱动,这也是为什么"长事务会阻塞Purge"这句话,准确的说法应该是"长事务存活期间,新产生的undo真实没法被清理",而不是"长事务一结束Purge就会立刻追上来"。

分步演示:一个长事务从存活到结束,Purge的真实反应

每一步都是本文上面用真实并发会话跑出来的原始结果。

长事务真实挡住了什么,又没挡住什么
这一步新验证的阶段

    本文用到的工具

    • 本机 MySQL 26.7.0(InnoDB),两个真实并发会话,一个长事务 + 一次并发提交
    • SHOW ENGINE INNODB STATUS里的History list length,配合真实的多轮重复观测
    • 没有验证:Purge后台线程调度延迟的具体触发条件(innodb_purge_rseg_truncate_frequency等参数和实际调度时机的精确关系),本文只如实记录了观测到的不确定性,没有进一步深挖调度器源码
    京ICP备2026067502号-1京公网安备11010502063487号