pt-online-schema-change通过影子表+触发器+重放机制实现不锁表变更:新建结构变更后的空表,分chunk拷贝数据,触发器同步原表dml,最后原子rename;要求binlog_format=row且表有主键或唯一非空索引。

pt-online-schema-change 为什么能不锁表改大表
它本质是用影子表+触发器+重放机制实现的:新建一个结构变更后的空表(影子表),把原表数据分 chunk 拷贝过去,同时用触发器捕获原表的增删改操作并同步到影子表,最后原子性地 rename 表名完成切换。整个过程原表始终可读写,但要注意触发器本身有开销,且主从延迟可能被放大。
关键前提是:binlog_format=ROW(否则触发器无法正确捕获变更),且表必须有主键或唯一非空索引(否则无法分 chunk 安全拷贝)。
常见误判点:
- 以为“在线”等于零影响——实际 CPU、IO、网络压力明显升高,高峰期慎用
- 忽略从库延迟——
pt-online-schema-change默认会等从库追平才切换,若从库落后太多会卡住 - 没检查磁盘空间——影子表 + 原表临时索引可能导致磁盘使用翻倍
执行前必须验证的 4 个条件
不是所有表都适合直接跑 pt-online-schema-change,漏掉任一检查都可能中途失败或引发数据不一致:
-
SHOW CREATE TABLE确认表有主键或唯一非空索引(PRIMARY KEY或UNIQUE NOT NULL) -
SELECT @@binlog_format必须返回ROW;如果返回MIXED或STATEMENT,需先在 session 级临时设为ROW(但更稳妥是全局调整) -
SHOW SLAVE STATUS\G查看Seconds_Behind_Master,建议 ≤ 30 秒;否则加--max-lag参数控制容忍阈值 - 确认
tmpdir和表所在磁盘剩余空间 ≥ 原表大小 × 1.5(含临时索引和日志)
最简安全执行命令与关键参数解释
以给 user_order 表加一个 status 字段为例,推荐起手式:
pt-online-schema-change \ --host=localhost \ --user=root \ --password=xxx \ --database=shopdb \ --table=user_order \ --alter="ADD COLUMN status TINYINT DEFAULT 0 AFTER id" \ --execute \ --chunk-time=0.5 \ --max-lag=5 \ --check-interval=5 \ --critical-load="Threads_running=50" \ --dry-run
说明:
-
--dry-run必须先加!它只建影子表、不拷数据、不启触发器,用于验证语法和权限是否 OK -
--execute是真正执行的开关,去掉它就只是预演;正式运行时必须显式带上 -
--chunk-time=0.5控制每批拷贝耗时上限(秒),值越小对线上影响越小,但总耗时变长 -
--max-lag=5表示只要从库延迟超过 5 秒就暂停拷贝,避免雪上加霜 -
--critical-load是熔断机制,例如Threads_running=50表示主线程数超 50 就暂停,防 DB 扛不住
执行中遇到错误怎么快速定位
最常卡住的三个地方和对应查法:
-
卡在 “Copying rows” 阶段不动:用
SHOW PROCESSLIST查是否有Waiting for table metadata lock;大概率是其他长事务或 DDL 正在执行,杀掉阻塞源或等其结束 -
报错 “Cannot chunk table because no unique index exists”:不是没有索引,而是索引包含
NULL列(比如UNIQUE (email)但email允许 NULL);此时需先ALTER TABLE ... MODIFY email VARCHAR(255) NOT NULL再重试 -
切换失败(rename 失败):看日志末尾是否出现
Cannot rename tables because the new table does not exist;一般是磁盘满或权限不足(如RENAME权限未授予用户)
工具日志默认输出到 stdout,建议加 --print 看每一步 SQL,加 --progress=time,5 每 5 秒打印进度,比盲等靠谱得多。











