这是《MySQL 8.0 源码与内核实战》的独立章节版。本章从概念、实操和生产排查三个视角展开,代码块保留了原书可直接运行的版本。 DELETE 和 UPDATE 不一定立即物理删除旧记录。Purge 线程负责清理 delete mark、undo 历史和无效页,是空间回收和版本链缩短的关键。
21.1 为什么需要 Purge
UPDATE row
-> old version in undo
-> current record points to old version
-> old snapshots may need it
-> purge after no reader needs it
Purge 做的事:
- 从 undo history 找可清理记录;
- 物理删除 delete-mark 记录;
- 清理索引记录;
- 推进 purge lag;
- 尝试页合并;
- 回收 undo 空间。
21.2 可见性与清理边界
Purge 只能清理早于最老活跃 read view 的版本:
oldest active read view
-> purge cannot remove newer needed versions
因此长事务、长查询、大事务和延迟提交都会阻碍 purge。
21.3 参数
| 参数 | 说明 |
|---|---|
innodb_purge_threads |
purge 线程数 |
innodb_purge_batch_size |
批处理大小 |
innodb_max_purge_lag |
延迟 DML 控制阈值 |
innodb_max_purge_lag_delay |
最大延迟 |
innodb_undo_log_truncate_frequency |
undo truncate 频率 |
21.4 观测
SHOW GLOBAL STATUS LIKE 'Innodb_history_list_length';
SHOW ENGINE INNODB STATUS;
SELECT trx_id, trx_started
FROM information_schema.innodb_trx
ORDER BY trx_started;
排查:
| 现象 | 方向 |
|---|---|
| history list 增长 | 长事务、purge 慢 |
| undo 膨胀 | purge 阻塞、空间压力 |
| delete 后磁盘不降 | 页碎片、未合并 |
| 写入延迟 | purge 滞后、二级索引 |
| CPU 高 | purge 并发过高 |
21.5 大删除
不推荐一次性删除超大范围:
DELETE FROM logs WHERE created_at < '2025-01-01';
推荐分批:
DELETE FROM logs
WHERE created_at < '2025-01-01'
ORDER BY id
LIMIT 1000;
每批之间提交,让 purge 和复制有机会推进。历史表可使用分区后 DROP PARTITION。
21.6 源码入口
| 文件 | 职责 |
|---|---|
trx0purge.cc |
purge 主流程 |
row0purge.cc |
行级清理 |
row0umod.cc |
undo 修改处理 |
row0uins.cc |
插入 undo |
断点:
b trx_purge
b row_purge_record
本章小结
Purge 清理旧版本和 delete-mark 记录,推进依赖最老活跃快照。长事务会让 history list 和 undo 空间持续增长;大批删除应分批或使用分区。
思考题
- 为什么 DELETE 后磁盘空间可能不变?
- history list length 反映什么?
- 长查询如何影响 purge?
- 大范围删除如何分批?
- purge 和 undo truncate 有什么关系?