根本原因是MDL锁阻塞:ALTER TABLE需元数据锁,遇长事务或慢查询即卡住,导致DML/DDL全阻塞,引发超时、连接池耗尽、500错误;并非结构修改失败,而是并发控制机制生效。

ALTER TABLE 期间应用报错,根本原因是锁表或元数据锁阻塞
MySQL 在执行 ALTER TABLE 时,默认会获取元数据锁(MDL),如果表上有长事务、未提交的查询或慢查询正在读写该表,ALTER TABLE 就会卡住,后续所有对该表的 DML/DDL 都被阻塞,应用表现为超时、连接池耗尽、500 错误。这不是“改结构出错”,而是并发控制机制在起作用。
实操建议:
- 执行前先查有没有活跃长事务:
SELECT * FROM information_schema.INNODB_TRX WHERE TIME_TO_SEC(NOW()) - TIME_TO_SEC(TRX_STARTED) > 60; - 确认没有
SELECT ... FOR UPDATE或未提交的UPDATE/INSERT正在持有该表的行锁或间隙锁 - 避免在业务高峰执行;若必须操作,优先用
ALGORITHM=INPLACE和LOCK=NONE(仅限支持的变更类型,如加字段、改默认值) - 对大表,
ALTER TABLE可能持续数分钟甚至更久,期间所有新请求都会排队等 MDL,不是“失败”,是“等待超时”
回滚 ALTER TABLE 不等于执行反向 SQL
MySQL 没有事务级的 DDL 回滚能力。一旦 ALTER TABLE 成功提交,就不可逆;所谓“回滚”,其实是人工干预:要么删掉新加的列、索引,要么把字段类型改回去,但前提是旧数据能兼容新结构——而很多变更(比如从 VARCHAR(255) 缩到 VARCHAR(50))已导致数据被截断,此时改回去也恢复不了原始值。
实操建议:
- 变更前务必备份表:
CREATE TABLE t_bak LIKE t; INSERT INTO t_bak SELECT * FROM t;(注意主从延迟和 binlog 格式) - 不要依赖
ROLLBACK—— DDL 语句本身不参与事务,START TRANSACTION对ALTER TABLE无效 - 若变更中途失败(如磁盘满、OOM),InnoDB 会尝试清理临时文件,但表可能处于“半完成”状态,需检查
information_schema.INNODB_TABLES和错误日志中的ERROR 1872等提示 - 线上严禁直接执行
DROP COLUMN或MODIFY COLUMN后再“后悔”,没备份就只能从从库或备份库拉数据
真正安全的变更方式:pt-online-schema-change 或 gh-ost
原生 ALTER TABLE 的风险在于锁表时间不可控,而 pt-online-schema-change(Percona Toolkit)和 gh-ost(GitHub 开源)通过影子表 + 触发器/binlog 解析实现“无锁变更”。它们不阻塞读写,但引入了新复杂度:触发器性能开销、主从延迟放大、DDL 过程中不能删触发器。
实操建议:
- 用
pt-online-schema-change前确保表有主键或唯一非空索引(否则无法增量同步) -
gh-ost更轻量,不依赖触发器,但要求 MySQL 开启binlog_format=ROW且binlog_row_image=FULL - 两种工具都需预留双倍磁盘空间(原表 + 影子表),并监控
Threads_running和从库延迟 - 变更完成后,务必手动验证数据一致性:
pt-table-checksum或比对COUNT(*)、MIN/MAX、随机抽样MD5
应用报错后第一反应不是 rollback,而是切流 + 定位阻塞点
线上 ALTER 执行中应用大面积报错,说明已有大量请求卡在 MDL 上。此时立刻执行反向 ALTER 不仅不能解燃眉之急,反而会加剧锁竞争。真实有效的响应顺序是:止血 → 定位 → 清理 → 恢复。
实操建议:
- 先看
SHOW PROCESSLIST,找出状态为Waiting for table metadata lock的线程,记下ID - 用
KILL [ID]干掉阻塞源头(通常是慢查询或未提交事务),而不是 killALTER线程本身(它可能正处在不可中断阶段) - 确认
ALTER是否仍在运行:SELECT * FROM information_schema.PROCESSLIST WHERE COMMAND = 'Query' AND INFO LIKE '%ALTER%'; - 若已卡死且无法 kill,重启 MySQL 是最后手段;但要注意:重启后未完成的
ALTER会被丢弃,表结构停留在变更前,但期间写入的数据可能丢失(取决于存储引擎和 crash safe 配置)
最常被忽略的一点:很多团队以为“加个索引很快”,结果在千万级订单表上执行 ALTER TABLE ADD INDEX,没设 LOCK=NONE,又没监控,等发现应用雪崩时,锁已积压上百个请求——这时候删索引救不了命,得先清锁。










