直接alter table大表会卡住数据库,因mysql需全表拷贝重建,期间阻塞读写并等待元数据锁;pt-online-schema-change通过影子表、分批拷贝和触发器实现低影响变更。

为什么直接 ALTER TABLE 大表会卡住数据库
因为 MySQL 在执行 ALTER TABLE 时,5.6 之前默认会锁全表;5.7+ 虽支持 ALGORITHM=INPLACE,但仅对部分操作生效(比如加索引、改列默认值),一旦涉及字段类型变更、删列、改字符集等,仍会触发表拷贝 —— 这意味着 MySQL 要读取整张表数据、写入临时表、重建索引、再原子替换。10GB 以上的表,这个过程可能持续几十分钟,期间 SELECT 可能被阻塞(尤其在 REPEATABLE-READ 隔离级别下),INSERT/UPDATE/DELETE 更是直接排队等待元数据锁(Waiting for table metadata lock)。
常见错误现象:SHOW PROCESSLIST 里看到大量 Waiting for table metadata lock;慢查询日志中出现长时间未完成的 ALTER;业务接口超时、连接池耗尽。
- 不是所有
ALTER都能走INPLACE,得查 官方 DDL 支持矩阵,别凭经验猜 - 即使支持
INPLACE,如果表上有长事务没提交,ALTER依然会被卡住 —— 它要等所有活跃事务释放该表的 MDL -
pt-online-schema-change不是“免锁”,而是把锁拆成小段:每次只锁几行,靠触发器同步增量,所以对线上影响可控
怎么用 pt-online-schema-change 安全改大表
它本质是“影子表 + 触发器 + 拆批拷贝”:新建一个结构新表(_tablename_new),把原表数据分块拷过去,同时用触发器捕获原表的增删改并同步到新表,最后原子切换表名。整个过程主表始终可读写。
基础命令示例:
pt-online-schema-change \ --host=localhost \ --user=root \ --password=xxx \ --alter "MODIFY COLUMN name VARCHAR(255) NOT NULL" \ D=test,t=user \ --execute
关键参数说明:
-
--alter:只写ALTER关键字之后的部分,比如"ADD COLUMN c1 INT",别加ALTER TABLE t1 -
D=test,t=user:必须显式指定库和表,不能省略D=和t= -
--execute:真正执行;不加这个只是预检(dry-run),会报错但不改表 -
--chunk-size=1000:每批处理 1000 行,默认 1000,大表可调到 5000–10000,但别太大,否则单次锁行时间过长 -
--max-load="Threads_running=25":当SHOW STATUS LIKE 'Threads_running'> 25 时自动暂停,避免拖垮数据库
pt-online-schema-change 的几个硬限制和坑
它不是万能银弹,有些场景直接报错退出,必须提前识别:
- 原表不能有外键(
Cannot have foreign key constraints)—— 得先删外键,改完再加回来 - 不能有触发器(
Table has triggers)——pt工具自己要建触发器,冲突了就失败 - 主键必须是单一列且为
INT或BIGINT类型;如果是联合主键或VARCHAR主键,会提示Cannot chunk the table using its primary key - 如果表有
ON DELETE CASCADE,pt无法保证级联逻辑在影子表上一致,建议提前评估业务影响 - 执行过程中如果 kill 掉
pt进程,它会自动清理临时表和触发器,但已拷贝的数据不会回滚 —— 所以别手抖Ctrl+C,要用--kill参数安全终止
执行前必须验证的三件事
很多人跳过这步,结果在线上炸锅:
- 确认从库延迟:用
SHOW SLAVE STATUS\G查Seconds_Behind_Master,必须是0或稳定值;否则pt同步触发器的 DML 会在从库堆积,导致复制断裂 - 检查磁盘空间:影子表 + 原表 + binlog,至少预留 2 倍原表大小的空间;
df -h /var/lib/mysql看清挂载点实际可用量 - 跑一次预检:
pt-online-schema-change ... --dry-run,重点看输出末尾有没有Operation, tries, wait:下的重试记录,以及是否提示This DDL will cause replication to break
最麻烦的不是工具跑不动,而是它跑完了,你才发现从库没跟上、或者某个字段改完应用层解析出错 —— 这些都得靠上线前在测试环境用真实流量压测来暴露。











