alter table直接在主库执行会堵死从库sql线程,因其以query_log_event单条记录写入binlog,从库必须串行重放,触发全库mdl锁升级,导致同库所有dml排队停摆,即使启用并行复制也无效。

不能“不停止复制”安全做完大表DDL——所谓“不停止”,只是把停的时间从小时级压缩到秒级,且必须绕过SQL线程重放机制。
为什么ALTER TABLE直接跑在主库就等于堵死从库SQL线程
主库执行ALTER TABLE后,binlog里只记一条Query_log_event(哪怕用了ALGORITHM=INPLACE),从库SQL线程必须串行执行它。期间整个库的DML全排队,SHOW PROCESSLIST里状态卡在altering table或Waiting for dependent transaction to commit,Seconds_Behind_Master会跳到几百甚至上千秒。
这不是慢,是停摆:从库relay log里就那一条长事务,没法切片、没法并发、logical_clock也无效。
- 即使设了
slave_parallel_workers=8,slave_parallel_type=database下,只要DDL操作的表在某个库内,该库所有后续事务都得等 -
innodb_flush_log_at_trx_commit=1或sync_binlog=1会进一步拖慢回放速度 - 别信“主库不锁,从库就没事”——从库重放时仍需获取相同MDL锁,若此时有长查询正读这张表,就会卡在
Waiting for table metadata lock
pt-online-schema-change 是目前最稳妥的实操路径
它不依赖binlog重放,而是用影子表+触发器+增量同步三步走,在主库上完成结构变更的同时,让业务读写完全不受影响。
- 先建影子表(
_t_new),应用DDL变更 - 用触发器捕获原表DML,实时同步到影子表
- 分批次拷贝历史数据(默认每次1000行),避免单次事务过大
- 最后原子切换:
RENAME TABLE t TO _t_old, _t_new TO t
注意:pt-online-schema-change默认会在从库自动同步结构变更(通过--recursion-method探测从库并执行),但不会自动校验主从数据一致性。你得自己跑pt-table-checksum确认。
手动STOP SLAVE + 从库直跑DDL 的适用边界
仅适用于满足全部下述条件的DDL:
- 操作本身支持
LOCK=NONE(如末尾加列、加普通索引) - 主从在DDL前已通过
pt-table-checksum或mysqldiff确认结构和数据一致 - 从库没有活跃长事务正在访问目标表(查
information_schema.INNODB_TRX) - 你接受短暂中断复制(
STOP SLAVE SQL_THREAD期间,IO线程仍在拉日志,延迟可控)
操作顺序必须是:STOP SLAVE SQL_THREAD → 主库执行DDL → 记下SHOW MASTER STATUS位点 → 从库手动执行相同DDL → START SLAVE → 立即验证Exec_Master_Log_Pos是否推进、Slave_SQL_Running_State是否回到Slave has read all relay log。
最容易被忽略的坑:STATEMENT模式下DDL+DML混合执行
主库如果在CREATE TABLE t1后立刻发INSERT INTO t1,而binlog_format=STATEMENT,从库SQL线程可能还没建完表就收到INSERT,直接报Table doesn't exist——这不是不同步,是执行时序错乱。
ROW模式能缓解,但若启用了slave_parallel_workers > 0,DDL和DML事件仍可能被分配到不同worker,导致“先插后建”。MySQL 8.0.27+的slave_ddl_exec_mode=IDEMPOTENT虽可忽略重复DDL,但会掩盖上游本应失败的操作(比如主库建表失败,从库却因IDEMPOTENT跳过而没报错)。
真正要命的是:有人在从库手建了同名表,主库后续ALTER TABLE到达时直接冲突失败,而错误日志里只显示“Duplicate column”,根本看不出根源在人为干预。











