根本原因是mdl写锁与全表复制共同作用:mysql默认add column会触发algorithm=copy并全程持有mdl_write锁,导致所有dml阻塞;即便支持online ddl,也需显式指定algorithm=inplace/instant和lock=none才可避免锁表。

MySQL执行ALTER TABLE导致业务中断,根本原因不是“语法写错了”,而是你没控制住锁类型和算法路径——默认行为在多数场景下会退化为全表拷贝+写锁,哪怕只是加个字段。
为什么ALTER TABLE ADD COLUMN也会锁死线上服务?
MySQL 5.6+虽支持Online DDL,但“支持”不等于“默认启用”。以下情况会让ADD COLUMN静默回退到ALGORITHM=COPY:
- 表含全文索引(5.6中直接触发COPY;5.7+需显式指定
ALGORITHM=INPLACE才可能绕过) - 存在外键约束(5.6–5.7.5不支持INPLACE添加字段)
- 有活跃的隐式长事务(
information_schema.innodb_trx里trx_started超60秒) - MySQL版本是5.6且未显式指定
ALGORITHM和LOCK参数
一旦走COPY,就会全程持有MDL_WRITE锁,所有DML阻塞,业务写入挂起——这不是慢,是停摆。
ALGORITHM=INSTANT和ALGORITHM=INPLACE到底该用哪个?
优先试INSTANT,它只改元数据,毫秒级完成,但仅限MySQL 8.0.12+且满足严格条件:
- 新增列必须在表末尾(不能
AFTER existing_col) - 不能带
DEFAULT子句以外的约束(如NOT NULL、CHECK) - 不能是
TEXT/BLOB类型(8.0.23+才部分支持)
若失败,降级用INPLACE,但必须配LOCK=NONE:
ALTER TABLE users ADD COLUMN avatar_url VARCHAR(255) ALGORITHM=INPLACE, LOCK=NONE;
如果报错ERROR 1846 (HY000): ALGORITHM=INPLACE is not supported,说明有隐性冲突(比如字段用了utf8mb4_0900_as_cs而原表是utf8mb4_general_ci),别硬扛,先查SHOW CREATE TABLE users确认字符集和排序规则是否一致。
线上千万级表不敢赌原生DDL?用gh-ost还是pt-online-schema-change?
选gh-ost——它不依赖触发器,对主库压力更小,且能动态限流、暂停、观察binlog延迟。但前提是:
- binlog格式必须是
ROW(SHOW VARIABLES LIKE 'binlog_format'验证) - 主库不能有
super_priv限制(gh-ost需要REPLICATION SLAVE权限读取binlog) - 避免在从库延迟>5s时启动(否则增量同步会积压)
执行示例:
gh-ost \ --host="master-host" \ --database="mydb" \ --table="orders" \ --alter="ADD COLUMN status TINYINT DEFAULT 0" \ --chunk-size=1000 \ --max-load="Threads_running=25" \ --critical-load="Threads_running=50" \ --throttle-control-replicas="slave-host" \ --allow-on-master \ --execute
它会在后台建影子表、同步全量+增量,最后原子切换RENAME——锁表时间仅毫秒级,但整个过程持续数小时,你要盯的是gh-ost输出里的throttled和copied状态,而不是等它跑完。
最容易被忽略的三件事:不是语法,是上下文
再正确的SQL,在错误的时机、错误的状态下执行,照样中断业务:
- 没检查
performance_schema.metadata_locks里有没有pending锁——别人正在执行长查询,你的ALTER会排队卡住,自己却以为“还没开始” - 没确认
seconds_behind_master是否 - 没验证目标字段是否已存在于表结构中(比如开发环境手动加过字段,线上漏了)——
ADD COLUMN会报错,但错误信息模糊,容易误判为锁问题
真正决定成败的,从来不是那条ALTER语句怎么写,而是执行前那三分钟你在看什么监控、查什么状态、问什么人。











