从INSTANT到MDL锁雪崩:一次主从延迟7200秒的排查复盘与pt-heartbeat监控落地

举报
这个DBA有点耶 发表于 2026/11/02 15:45:08 2026/11/02
【摘要】 主库执行Online DDL秒级完成,业务毫无感知;一觉醒来,主从延迟却突破了7200秒。本文将从MDL(元数据锁)的阻塞机制、算法降级的隐秘角落,以及并行复制在DDL面前的失效原因入手,深度剖析DDL回放导致主从延迟的底层链路。并给出gh-ost源码级的限流逻辑、主备倒换的平滑操作手册,以及如何通过pt-heartbeat建立真实的延迟监控。

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

前阵子遇到一次极其典型的生产事故:凌晨2点,我在主库给一张8000万行的核心交易表加了个字段。

使用的是 MySQL 8.0 的 Online DDL,ALGORITHM=INSTANT,主库毫秒级完成。当时我看了一眼监控,主从延迟是0,就安心睡觉了。

结果早上9点,业务群炸了:“从库读出来的数据全是旧状态,报表严重失真!”

登录从库一看,SHOW SLAVE STATUS 里的 Seconds_Behind_Master 赫然显示:7200秒。而 SHOW PROCESSLIST 显示,从库的 SQL 线程一直卡在 Waiting for table metadata lock。

主库秒级完成的 DDL,为什么到了从库就变成了一场灾难?今天我们把这件事的底层链路彻底拆开。

一、深层剖析:从库的 MDL 锁雪崩

在 ROW 格式的 Binlog 下,主库的 DDL 会作为一个 Query_event 写入 Binlog。从库的 SQL 线程在读取到这个事件并尝试重放时,必须经历以下链路:

  1. 申请 MDL 排他锁:从库执行 ALTER TABLE 前,必须获取该表的 MDL X锁。

  2. 阻塞后续所有事务:由于从库的 SQL 线程(如果是单线程,或者遇到跨线程的表级依赖)正在持有这张表的 MDL X锁,所有针对这张表的后续 DML 操作(INSERT/UPDATE/DELETE)在回放时,统统只能进入 MDL 的 PENDING 队列排队。

  3. 雪崩效应:主库还在持续不断地产生新的 Binlog(包含大量针对该表的 DML),从库 SQL 线程卡在 DDL 上,后面的日志堆积如山。

为什么 Seconds_Behind_Master 没提前报警?
如果在 DDL 执行前,从库已经处于空闲状态(延迟为0),DDL 事件到达后,从库开始卡住。此时 Seconds_Behind_Master 可能会显示为0或很小的数字,直到主库有新的事务过来,时间戳开始产生差值,这个数字才会开始飙升。这就导致了一个监控盲区。

二、隐秘的角落:INSTANT 算法为什么会“悄悄降级”?

我明明写了 ALGORITHM=INSTANT,主库也确实是秒完的,为什么从库会卡这么久?

MySQL 8.0.12 引入的 INSTANT 算法极其严苛。在以下场景中,即使你指定了 INSTANT,MySQL 也会静默降级为 INPLACE 甚至 COPY,且不报错:

  • 表包含全文索引(FULLTEXT)或空间索引(SPATIAL)。

  • 在表的中间位置添加列(8.0.29之前)。

  • 修改列的数据类型、字符集。

如果是 COPY 算法,意味着主库和从库都需要新建一张临时表,把原表数据逐行拷贝过去。8000万行的表,拷贝一次可能耗时十几分钟,这期间从库的 MDL 锁死死不放,主从延迟瞬间爆炸。

实操验证:如何防止算法降级?
不要盲目相信自己的 SQL,在执行 DDL 时,强制带上 ALGORITHM 和 LOCK 子句:

ALTER TABLE orders ADD COLUMN remark VARCHAR(100), ALGORITHM=INSTANT, LOCK=NONE;

如果当前表结构不支持 INSTANT,这条语句会直接报错退出。宁可报错,也不要让它悄悄降级导致主从瘫痪。

三、实操解法:大表 DDL 的正确打开方式

对于千万级以上的大表,哪怕支持 INSTANT,也建议走以下方案:

方案一:使用 gh-ost 规避锁等待(大厂标配)

gh-ost 的核心原理是:创建幽灵表 -> 模拟从库拉取 Binlog -> 应用增量 -> 原子 Rename。
深层机制:gh-ost 在主库上执行的是大量的 INSERT INTO ghost_table SELECT ... 的按行拷贝,产生的 Binlog 都是正常的 DML 操作。从库在回放这些 DML 时,不会触发表级 MDL 排他锁,因此业务读取正常,延迟极小。

但要注意 gh-ost 的限流机制。它默认会检查主库的 Threads_running,如果超过阈值就会暂停拷贝。你可以通过 --max-load 参数调整,避免拷贝打满主库 CPU。

方案二:利用主备倒换(Smart DBA 的杀手锏)

如果是在云环境或有成熟的高可用架构(如MHA/Orchestrator),可以采用“切主”方案:

  1. 在业务低峰期,主动触发一次主备切换。原备库提升为主库(承接读写业务)。

  2. 原主库(现已被降级为备库),它上面的数据与主库是完全同步的。

  3. 在这个“新备库”上执行你的 DDL 操作。因为它不承载业务读取,你可以随便锁表、随便跑 COPY 算法,不会影响业务。

  4. 等 DDL 执行完毕,主从延迟追平后,在下一个低峰期,再把它切回主库。

方案三:精准延迟监控(替换掉不可靠的 Seconds_Behind_Master)

既然 Seconds_Behind_Master 有盲区,做大表 DDL 时,必须开启 pt-heartbeat 来监控真实的延迟:

# 在主库启动心跳写入
pt-heartbeat --user=root --password=xxx --create-table --database=test --update --daemonize

# 在从库监控真实延迟
pt-heartbeat --user=root --password=xxx --database=test --monitor --print-master-server-id

它会每秒在主库写入一个带时间戳的探针,从库读取后对比当前时间,哪怕从库没写入,你也能精准看到当前回放落后了多少秒。一旦超过阈值,立即通过 kill 命令终止从库的 DDL 回放,防止雪崩。

四、小结

不要被主库上 Online DDL 的“秒级”现象迷惑。在主从架构下,DDL 回放涉及到 MDL 锁的排他性、算法的降级以及并行复制的失效。对于大表,尽量避免在主库直接执行 DDL;优先使用 gh-ost 或“主备倒换”;并在 DDL 期间用 pt-heartbeat 替换掉原生的延迟监控。宁可提前排查,不要等到业务大面积超时再来救火。

小耶在手,SQL 不愁

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

【声明】本内容来自华为云开发者社区博主,不代表华为云及华为云开发者社区的观点和立场。转载时必须标注文章的来源(华为云社区)、文章链接、文章作者等基本信息,否则作者和本社区有权追究责任。如果您发现本社区中有涉嫌抄袭的内容,欢迎发送邮件进行举报,并提供相关证据,一经查实,本社区将立刻删除涉嫌侵权内容,举报邮箱: cloudbbs@huaweicloud.com
  • 点赞
  • 收藏
  • 关注作者

评论(0)

0/1000
抱歉,系统识别当前为高风险访问,暂不支持该操作

全部回复

上滑加载中

设置昵称

在此一键设置昵称,即可参与社区互动!

*长度不超过10个汉字或20个英文字符,设置后3个月内不可修改。

*长度不超过10个汉字或20个英文字符,设置后3个月内不可修改。