大表 DDL 面试翻车现场Online DDL 为什么还会锁死业务

Online DDL 不等于不锁表:DDL 抢 MDL 写锁被长事务挡住,后面的查询全排队,几秒打满连接池。高可用变更是三层兜底,不是挑半夜跑。

那道题,问到第三个追问就断了

面试官的原题:单表千万级数据,要加字段、加索引,你们怎么执行 DDL?

一个三年经验的候选人答得很干脆:用 MySQL 自带的 Online DDL,低峰期跑,不会有问题。

追问链紧跟着来了:

  • Online DDL 为什么还会被长事务堵住,引发全表 MDL 锁等待?
  • 大表变更执行到一半失败,怎么快速回滚不残留数据?
  • 变更高峰期主从延迟飙升,怎么做分级兜底?
  • 你遇到过的大表 DDL 锁表导致的业务阻塞、主从雪崩,能不能画个时间线还原?

候选人愣住了,想了半天说:目前只能靠低峰期执行和人工 SQL 审核保底。

面试到这儿基本可以结束了。这道题考的从来不是会不会写 ALTER 语句,而是数据库变更场景下的可用性风险识别与全链路防御设计。

ALTER 跑通了,不代表你会变更

很多后端有个误区:ALTER 执行成功、表结构改对了,就觉得自己"会做大表变更"。

小表随意改,和生产大表的高可用变更,是两个完全不同的技术维度。混为一谈,是事故的起点。

DDL 从来不是一条 SQL,它是一次带锁的结构发布。发布就要有灰度、监控、回滚预案和止损手段。只靠"低峰期"三个字,本质上是裸奔。

真正的地雷是 MDL,不是 ALTER 慢

Online DDL 里的"Online",指的是变更过程中大部分时间不阻塞 DML------比如拷贝数据阶段允许并发读写。

但有两个瞬间它必须拿写锁:变更开始时的元数据锁(MDL)升级,以及变更结束时的元数据提交。

MDL 是 MySQL 5.5 之后引入的 Server 层机制,跟存储引擎无关。规则很简单:

  • 普通 SELECT、DML 持有 MDL 读锁(SHARED_READ / SHARED_WRITE)
  • DDL 需要 MDL 写锁(EXCLUSIVE)

关键在于 MDL 请求队列是按顺序阻塞的。 DDL 一旦卡在前面某个未提交的长事务上,所有新来的 SELECT 都只能排在 DDL 后面------它们明明只是读,也一起被堵死。

这就是"被长事务堵住引发全表 MDL 锁等待"的完整因果:

sequenceDiagram participant T1 as 长事务T1 participant D as DDL会话 participant Q as 后续查询 participant M as MDL队列 T1->>M: 持有 SHARED 读锁 迟迟不提交 D->>M: 申请 EXCLUSIVE 写锁 M-->>D: 排在 T1 之后 等待 Q->>M: 申请 SHARED 读锁 M-->>Q: 排在 D 之后 阻塞 Note over Q: 连接池 30 秒内打满 T1->>M: COMMIT 释放 M-->>D: 获得写锁 开始变更

现场症状很典型:ERROR 1205 (HY000): Lock wait timeout exceededSHOW PROCESSLIST 里一排 Waiting for table metadata lock

更坑的是 MySQL 5.7 和 8.0 的 lock_wait_timeout 默认值都是 31536000 秒,也就是一年。DDL 会老老实实等下去,你的连接池撑不过三十秒。

所以变更前要做的第一件事,不是写 ALTER,是查长事务:

sql 复制代码
-- 捞出跑得最久的未提交事务
SELECT trx_mysql_thread_id, trx_started, trx_query
FROM information_schema.innodb_trx
ORDER BY trx_started ASC
LIMIT 10;

-- 看谁在等 MDL、谁在持有
SELECT OBJECT_NAME, LOCK_TYPE, LOCK_DURATION, LOCK_STATUS, THREAD_ID
FROM performance_schema.metadata_locks
WHERE OBJECT_SCHEMA = 'order_db';

-- 给 DDL 会话设置超时,宁失败勿雪崩
SET SESSION lock_wait_timeout = 3;
ALTER TABLE t_order ADD COLUMN ext_json JSON DEFAULT NULL,
  ALGORITHM = INPLACE, LOCK = NONE;

注意最后一句的 ALGORITHM=INPLACE, LOCK=NONE:显式声明可以让 MySQL 在不满足条件时直接报错退出,而不是悄悄降级成 COPY 模式,把表锁上几个小时。

四套方案:会背没用,得会拆取舍

只背"用 Online DDL"是不够的,得能拆解每种方案在 MDL 锁、数据一致性、回滚成本、大表适配上的取舍。

维度 直接 ALTER 原生 Online DDL pt-osc gh-ost
阻塞 DML 全程锁表 起止两个瞬间需 MDL 写锁 短时 MDL 写锁 短时 MDL 写锁
实现方式 拷贝原表 INPLACE 或 COPY 触发器 + 分块拷贝 消费 binlog,无触发器
回滚成本 中,部分操作不可回滚 丢弃影子表即可 丢弃影子表即可
主从延迟 峰值极高 可控 可控,支持节流
大表适配 不推荐 千万级尚可,亿级危险 亿级可用 亿级首选项

必须清楚一点:ALGORITHM=INPLACE 不等于不重建表。MySQL 8.0 的 ALGORITHM=INSTANT 加列是秒级元数据操作,但改列类型、加主键依然要重建整表。

三个变更前必查项:

  1. Online DDL 的 MDL 等待有没有超时自动放弃和重试机制。 宁可在第 3 秒失败,也不要挂在那里把所有读请求拖死。
  2. gh-ost 的 cut-over 时机是否基于流量波谷做了精准控制。--max-load--critical-load--throttle-control-replicas 节流,而不是拍脑袋定时间。切表那一瞬间会不会丢数据、会不会拉爆主从延迟,都要有数。
  3. 核心业务大表是否用了组合拳:变更前长事务查杀 + 双表结构同步 + 流量灰度切流。 目标是业务几乎无感的变更,而不是"祈祷这几分钟没有大查询"。

"半夜跑没事"是感觉,不是结论

要把变更风险翻译成能告警的数字:

  • MDL 锁平均等待时长
  • 变更执行总时长
  • 主从延迟峰值
  • 业务请求错误率

然后提前回答一个问题:DDL 阻塞之后,是先 kill 阻塞会话,还是先终止变更? 这个决定不能现场拍脑袋。

flowchart TD S[告警 MDL 等待队列堆积] --> Q1{DDL 是否已拿到写锁} Q1 -->|否| K1[kill 长事务 释放 MDL] Q1 -->|是| Q2{变更能否安全中断} Q2 -->|影子表方案| K2[终止变更 丢弃影子表] Q2 -->|原生 ALTER 大表| K3[优先保业务 评估后 kill DDL]

按这个标准,方案可以分三类:

分类 特征 结论
可用方案 非核心小表走低峰期 Online DDL + 超时兜底;核心大表走影子表 + 灰度切流 可上
风险方案 只靠默认 Online DDL,不做前置检测,长事务一堵就全表锁死 必须改造
无效方案 无预案、无监控,大表直接 ALTER,锁表时长全靠碰运气 禁止上线

第三类不是"效果差",是"业务裸奔"。 区分核心表与非核心表,把高可用变更流程和普通变更解耦,才是治理的起点。

把 DDL 当成一次发布

认知的最后落点是流程,不是某个人的经验。

  1. 建立数据库......

转录在这里断了,落地规范的完整清单没录到。下面的收尾是我按已有信息补的,不是视频原话。

写在最后

规范这条线,原文只留了个开头,所以我只提一个真正落地时会卡住的问题:ALGORITHM=INPLACE, LOCK=NONE 硬报错和接受降级成 COPY,你们线上选哪个?这背后其实是"变更失败"和"业务被锁"哪个代价更能接受。

如果这篇帮你少踩一次锁表,点个赞就够了。

相关推荐
IvorySQL1 小时前
PostgreSQL 日报|建库策略致备库静默丢数据(9 月 12 日)
数据库·人工智能·postgresql
这个DBA有点耶1 小时前
当Agent从“写SQL”变成“执行SQL”:数据库安全模型需要重新设计
数据库·aigc·dba
晚安日记wanna1 小时前
MySQL 主从延迟别只答并行复制
数据库·面试·架构
SamDeepThinking1 小时前
Java 17内存屏障:为什么需要,怎么用
java·后端·面试
CC数分1 小时前
2026年金融数据分析岗位需要哪些工具?2027届秋招备考指南
面试·数据分析
狼与自由2 小时前
MCP协议理解
人工智能·架构
qq_401700412 小时前
Qt QUrl 详解与代码示例
开发语言·数据库·qt
程序员阿鹏2 小时前
如何实现MySQL分库分表?
数据结构·数据库·sql·mysql·缓存
风哥2号3 小时前
数据库教程FGMT33‑MySQL主从复制项目实施与维护06(MySQL8.4/9.7 MGR组复制)
数据库·mysql