mysql 5.7及之前加索引会锁表拷贝,8.0虽支持inplace但主键变更等仍退化为copy;pt-online-schema-change需主键、避高峰期、校验字符集与触发器,--dry-run不验证复制延迟和触发器一致性。

大表加索引为什么卡住不返回
因为 MySQL 5.7 及之前版本对 ALTER TABLE 加索引是「锁表 + 全量拷贝」,表越大,锁时间越长,业务写入直接阻塞。8.0 虽支持原地加索引(ALGORITHM=INPLACE),但仅限某些场景,比如非唯一二级索引;一旦涉及主键变更、列类型调整或全文索引,照样退化为拷贝表。
常见错误现象:SHOW PROCESSLIST 里卡在 Waiting for table metadata lock,或者 ALTER 执行数小时没反应,SELECT COUNT(*) 都变慢——说明 MDL 锁正在被长时间持有。
- 判断是否真需要在线变更:先查
information_schema.INNODB_TRX看有没有长事务,有就别急着改表 - 确认 MySQL 版本和存储引擎:MyISAM 不支持在线 DDL,InnoDB 8.0+ 才有较完整的
ALGORITHM=INSTANT支持 - 别依赖
pt-online-schema-change自动判断——它默认会检查从库延迟、主键存在性等,但若主键是UUID或复合主键,可能误判为“不可安全操作”
pt-online-schema-change 怎么跑才不翻车
pt-online-schema-change 的核心逻辑是建影子表 → 增量同步 → 原子切换,但它不是无脑执行的黑盒。很多翻车是因为没看清它的默认行为和约束条件。
- 必须有主键或唯一非空索引:否则无法用
WHERE精准定位变更行,工具会直接退出并报错Cannot chunk the table - 默认每块同步 1000 行,但若单行太大(比如含
TEXT字段),容易触发max_allowed_packet超限,建议加--chunk-size=100并调大客户端和 server 的该参数 - 切换阶段(
RENAME)耗时极短,但前提是源表和影子表结构完全一致,包括字符集、排序规则、外键约束——漏掉一个COLLATE utf8mb4_0900_as_cs就会导致切换失败回滚 - 别在高峰期跑:虽然不锁主表,但它会持续发
INSERT ... SELECT和DELETE,IO 和 CPU 压力不小,监控Threads_running和磁盘 await
ALTER TABLE … ALGORITHM=INPLACE 在哪些情况下会静默退化
MySQL 文档说支持 INPLACE,但实际执行时可能悄悄切回 COPY,且不报错。这时候看 SHOW ENGINE INNODB STATUS 的 DDL log 段落,或查 performance_schema.events_statements_history 里的 ALGORITHM 实际值才能确认。
- 添加唯一索引时,如果已有重复数据,会强制退化为
COPY(哪怕你写了ALGORITHM=INPLACE) - 修改列的
NULL/NOT NULL属性,在 5.7 中不支持原地改,8.0 支持但要求列不能有默认值(DEFAULT会触发重建) - 对
JSON或GENERATED列加索引,即使语法合法,内部仍可能走拷贝路径,建议提前在测试库用EXPLAIN FORMAT=JSON查执行计划里的alter_algorithm字段
为什么 pt-online-schema-change 的 --dry-run 不等于安全
--dry-run 只模拟建影子表、检查主键、生成同步 SQL,但不真正执行任何 DML。它绕过了最关键的两个风险点:复制延迟放大和触发器冲突。
- 主库写入快、从库延迟高时,
pt-osc的INSERT和UPDATE可能被复制延迟“拖住”,导致影子表数据滞后,最终切换后出现脏数据 - 如果表上有自定义触发器(尤其是
BEFORE INSERT),pt-osc的同步语句不会触发它们,但切换后新写入会触发——结果就是影子表没走触发逻辑,主表走了,数据不一致 - 更隐蔽的是
binlog_format=STATEMENT下,pt-osc的REPLACE INTO可能因函数(如NOW())被重写,造成从库数据偏差
真正上线前,至少要在从库上做一次完整回放验证,而不是只信 --dry-run 的 OK 输出。










