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 锁等待"的完整因果:
现场症状很典型:ERROR 1205 (HY000): Lock wait timeout exceeded,SHOW 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 加列是秒级元数据操作,但改列类型、加主键依然要重建整表。
三个变更前必查项:
- Online DDL 的 MDL 等待有没有超时自动放弃和重试机制。 宁可在第 3 秒失败,也不要挂在那里把所有读请求拖死。
- gh-ost 的 cut-over 时机是否基于流量波谷做了精准控制。 靠
--max-load、--critical-load、--throttle-control-replicas节流,而不是拍脑袋定时间。切表那一瞬间会不会丢数据、会不会拉爆主从延迟,都要有数。 - 核心业务大表是否用了组合拳:变更前长事务查杀 + 双表结构同步 + 流量灰度切流。 目标是业务几乎无感的变更,而不是"祈祷这几分钟没有大查询"。
"半夜跑没事"是感觉,不是结论
要把变更风险翻译成能告警的数字:
- MDL 锁平均等待时长
- 变更执行总时长
- 主从延迟峰值
- 业务请求错误率
然后提前回答一个问题:DDL 阻塞之后,是先 kill 阻塞会话,还是先终止变更? 这个决定不能现场拍脑袋。
按这个标准,方案可以分三类:
| 分类 | 特征 | 结论 |
|---|---|---|
| 可用方案 | 非核心小表走低峰期 Online DDL + 超时兜底;核心大表走影子表 + 灰度切流 | 可上 |
| 风险方案 | 只靠默认 Online DDL,不做前置检测,长事务一堵就全表锁死 | 必须改造 |
| 无效方案 | 无预案、无监控,大表直接 ALTER,锁表时长全靠碰运气 | 禁止上线 |
第三类不是"效果差",是"业务裸奔"。 区分核心表与非核心表,把高可用变更流程和普通变更解耦,才是治理的起点。
把 DDL 当成一次发布
认知的最后落点是流程,不是某个人的经验。
- 建立数据库......
转录在这里断了,落地规范的完整清单没录到。下面的收尾是我按已有信息补的,不是视频原话。
写在最后
规范这条线,原文只留了个开头,所以我只提一个真正落地时会卡住的问题:ALGORITHM=INPLACE, LOCK=NONE 硬报错和接受降级成 COPY,你们线上选哪个?这背后其实是"变更失败"和"业务被锁"哪个代价更能接受。
如果这篇帮你少踩一次锁表,点个赞就够了。