Redo Log循环写与checkpoint推进:长事务阻塞下的Checkpoint风暴完整链路

大家好,我是小耶,写功课只是为了我踩过的坑,你们别再踩了!

有一次线上告警:数据库突然不响应任何写操作了。应用侧报"Lock wait timeout",但查锁等待,没有明显的行锁冲突。

连上去看,SHOW ENGINE INNODB STATUS的输出里,LOG段显示Last checkpoint at和Log sequence number之间的差距非常大。再看Innodb_log_waits,持续增长。

问题找到了:Redo Log写满了,InnoDB暂停了所有写入,等待脏页刷盘。而根因,是一个跑了半小时还没提交的长事务。

Redo Log这东西,平时没人关注它。它不像Buffer Pool那样有命中率可看,也不像锁那样有等待可查。它安安静静地循环写,直到写满的那一刻,整个数据库的写入瞬间停止。

要理解为什么会写满,得先看它是怎么循环的。

一、Redo Log的循环写与checkpoint的约束

Redo Log的总大小由innodb_log_file_size × innodb_log_files_in_group决定。假设每个文件1GB,两个文件,总共2GB。

写入是循环的:写到文件末尾,回到文件开头继续写。但不能直接覆盖还没刷盘的日志。如果某段日志对应的脏页还在内存里没落盘,这段日志就必须保留------万一数据库崩溃,恢复时还需要它。

checkpoint的位置,标记的就是"哪些日志可以安全覆盖"。checkpoint之前的日志,对应的脏页已经刷盘,可以覆盖;checkpoint之后的日志,对应的脏页还没刷盘,不能动。

写入位置不断向前推进,checkpoint也必须跟着推进,才能腾出空间。如果checkpoint推不动,写入位置迟早会追上checkpoint,Redo Log就满了。

二、checkpoint为什么推不动

checkpoint推进的条件是:所有LSN小于checkpoint位置的脏页,都已经刷盘。有三个因素会阻止这件事发生。

长事务是第一个,也是最常见的一个。

一个事务修改了大量数据页但还没提交。这些页是脏页,但对应的Redo Log不能被覆盖------事务回滚时还需要这些日志。长事务不结束,checkpoint就无法越过它涉及的日志范围。

我那次遇到的长事务,是一个批量更新任务,一次修改了上百万行,跑了半小时。这半小时里,checkpoint被卡死,Redo Log可用空间持续缩小。

第二个因素是脏页刷盘速度跟不上日志产生速度。

即使没有长事务,如果写入量太大,脏页产生速度超过刷盘速度,checkpoint也会滞后。innodb_io_capacity决定了每秒能刷多少页,默认200。如果业务写入远超这个速度,checkpoint追不上。

第三个因素是日志文件太小。

默认值在很多版本中是48MB,对于写入量大的业务远远不够。可用空间本身就有限,稍微一点滞后就会写满。

三、从写满到暂停的完整链路

完整的触发链路是这样的:

  1. 长事务开始,修改大量数据页,产生大量Redo Log

  2. 事务不提交,它涉及的日志不能被覆盖,checkpoint被卡住

  3. 新的写入继续产生Redo Log,写入位置向前推进

  4. 写入位置接近checkpoint位置,可用空间越来越小

  5. Redo Log写满,InnoDB暂停所有写入

  6. InnoDB加速刷脏页,等待checkpoint推进

  7. checkpoint推进后,写入恢复

这个问题的可怕之处在于:它不是慢慢变慢,是突然完全停止。 前面的写入一直正常,直到Redo Log写满的那一刻,所有写入瞬间阻塞。应用侧的感知是"数据库突然不响应了",但数据库本身并没有宕机。

四、怎么排查和确认

第一步:看checkpoint和LSN的差距

bash 复制代码
SHOW ENGINE INNODB STATUS\G

在LOG段中,对比Log sequence number和Last checkpoint at。差距越大,说明checkpoint越滞后。如果差距接近Redo Log总大小,说明快写满了。

第二步:看等待次数

bash 复制代码
SHOW GLOBAL STATUS LIKE 'Innodb_log_waits';

这个值持续增长,说明已经发生过多次"Redo Log写满等待"。

第三步:找长事务

bash 复制代码
SELECT trx_id, trx_state, trx_started,
       TIMESTAMPDIFF(SECOND, trx_started, NOW()) AS duration_sec,
       trx_rows_modified
FROM information_schema.innodb_trx
WHERE TIMESTAMPDIFF(SECOND, trx_started, NOW()) > 60
ORDER BY duration_sec DESC;

这个查询列出运行超过60秒的事务,按持续时间排序。trx_rows_modified显示事务修改了多少行------修改行数越多,对checkpoint的阻塞越严重。

第四步:看脏页比例

bash 复制代码
SHOW GLOBAL STATUS LIKE 'Innodb_buffer_pool_pages_dirty';
SHOW GLOBAL STATUS LIKE 'Innodb_buffer_pool_pages_total';

脏页占比过高(超过50%),说明刷盘跟不上。

五、解决方案:控制事务大小是根本

方案一:控制事务大小。

这是最根本的方案。不要把上百万行的修改放在一个事务里。拆成每批几千行,提交后再跑下一批。长事务被拆成短事务,checkpoint就不会被长时间卡住。

我那次遇到的长事务,后来改成了分批更新,每批5000行,提交后再处理下一批。同样是一百万行,Redo Log不再堆积,checkpoint正常推进。

方案二:调大innodb_log_file_size。

给Redo Log更多缓冲空间。但这是治标------如果长事务持续存在,再大的日志文件也会被写满。MySQL 8.0.30之后用innodb_redo_log_capacity统一控制,可以动态调整。

方案三:调大innodb_io_capacity。

加快脏页刷盘速度。SSD环境可以调到2000-5000。这样checkpoint推进得更快,Redo Log空间释放得更及时。

方案四:监控Innodb_log_waits和长事务。

把这两个指标纳入监控。Innodb_log_waits持续增长是明确的预警信号。长事务查询定期跑,发现超过60秒的事务及时处理。

六、小结

Redo Log写满导致数据库暂停写入,根因往往不在磁盘慢,在长事务阻塞了checkpoint推进。长事务涉及的日志不能被覆盖,checkpoint被卡住,写入位置追上checkpoint时Redo Log就满了。排查的核心是对比Log sequence number和Last checkpoint at的差距、监控Innodb_log_waits、找出长时间运行的事务。最根本的解决方案是控制事务大小------拆批执行,让checkpoint有机会推进。调大日志文件和刷盘能力是辅助手段,不能替代事务拆分。

小耶在手,SQL 不愁

还有什么想了解的,欢迎留言!小耶一定知无不言言无不尽......我们下次见~

相关推荐
jnrjian1 小时前
pg_waldump --key-file-name status 查看wal 产生的db
数据库
SelectDB技术团队2 小时前
Snowflake 的开源替代怎么选:能力对照、三条部署路径与迁移执行
大数据·数据库·云原生·开源·snowflake·apache doris
Elastic 中国社区官方博客2 小时前
告别繁琐控制台:Kabanos 如何将 MongoDB Compass 的易用性带入 Elasticsearch
大数据·数据库·mongodb·elasticsearch·搜索引擎·全文检索
京东云开发者2 小时前
当我谈数据库高可用时我谈些什么
数据库
梦影_3 小时前
MySQL进阶教程(一)—— 储存过程与触发器(上)
开发语言·数据库·后端·sql·mysql·中间件
星禾元亨3 小时前
用结构化数据与干净实体信号,让技术内容被 AI 搜索引擎稳定引用
数据库·人工智能·自动化·创业创新
stark张宇3 小时前
库存扣减的“锁”事:悲观锁、乐观锁、分布式锁
redis·分布式·mysql
底层玩家老张3 小时前
45:1压缩、19.1倍查询:1956亿行时序数据复盘
数据库·经验分享·时序数据库·存储压缩·写入优化
qq_白羊座3 小时前
口头说的 JSON 和 MySQL 里的 JSON
数据库·mysql·json