pt-online-schema-change 在线改表:1 条触发器硬约束、4 道安全阀与手动 swap 流程
项目信息:
- 官方文档:
- GitHub:
它是怎么做到不锁表的
pt-online-schema-change 模拟了 MySQL 内部改表的方式,但操作对象是你想改的那张表的副本。所以原表不加锁,客户端可以继续读写。
流程大致是:
- 创建一张结构相同的空表;
- 按需要修改这张新表的结构;
- 从原表分块拷贝数据到新表。分块大小会动态调整,目标是让每块执行时间落在指定范围内(见
--chunk-time); - 拷贝期间原表上的数据变更,通过工具在原表上创建的触发器同步到新表对应行;
- 拷贝完成后,用一次原子的
RENAME TABLE同时重命名原表和新表; - 默认还会把原表 drop 掉。
因为依赖触发器,如果原表上已经存在任何触发器,这个工具就用不了。
外键是风险放大器
外键会让整个流程复杂化,并引入额外风险。当有外键指向这张表时,「原子重命名原表和新表」这一招直接失效——schema 变更结束后,工具还必须把外键改成指向新表。这由 --alter-foreign-keys-method 指定,支持四种取值:
auto:能用rebuild_constraints就用,否则退到drop_swap。rebuild_constraints:ALTER TABLE先 drop 再重新加上引用新表的外键。除子表过大以外,这是首选方案。注意 FK/索引名可能会带上前导下划线而变化。drop_swap:置FOREIGN_KEY_CHECKS=0,在 rename 新表之前先 drop 原表。更快、不阻塞,但存在一个短暂窗口表「不存在」;如果 rename 失败,旧表就永久没了。该方式强制打开--no-swap-tables和--no-drop-old-table。none:类似drop_swap但不做 swap,结果是外键指向一张不存在的表。
四道安全阀
第一,默认只读不写。 除非显式指定 --execute,工具不会真正改动表。--execute 默认关闭。
第二,主键/唯一索引。 绝大多数情况下,表上没有 PRIMARY KEY 或 UNIQUE INDEX,工具会拒绝执行(DELETE 触发器需要它)。唯一例外:本次 ALTER 本身就是在已有列上创建 PK/UNIQUE。
第三,复制相关。 检测到 replication filters 会拒绝操作;发现任何从库复制延迟时会暂停数据拷贝(--max-lag)。
第四,负载相关。 检测到服务器负载过高会暂停或中止操作(--max-load 和 --critical-load)。
另外,工具会设置 innodb_lock_wait_timeout=1,MySQL 5.5 及以上还会设置 lock_wait_timeout=60,目的是让自己在锁竞争中最先成为牺牲者,尽量不干扰其他事务。这两个值可以通过 --set-vars 修改。
还有两类硬性限制:
- 只要有外键约束指向该表,工具就拒绝改表,除非指定
--alter-foreign-keys-method。 - 在 Percona XtraDB Cluster 节点上无法修改 MyISAM 表。PXC 5.5.28-23.7+:只支持 InnoDB 表,且
wsrep_OSU_method必须是TOI。
--alter 的写法与限制
基本用法:
pt-online-schema-change --alter "ADD COLUMN c1 INT" D=sakila,t=actor
--alter 里写的是不带 ALTER TABLE 关键字的 schema 变更语句,多个变更用逗号分隔。限制如下:
- 不能用
RENAME子句重命名表。 - 列不能通过 drop 再 add 的方式改名——工具不会拷贝原列的数据。
- 如果新增列没有默认值,又把它设为
NOT NULL,工具会失败。 DROP FOREIGN KEY constraint_name要写成下划线开头的_constraint_name,而不是真实名字,例如--alter "DROP FOREIGN KEY _fk_foo"。
--dry-run 和 --execute 互斥,不能同时给。
MySQL 8 的两个坑
MySQL 8 默认认证插件是 caching_sha2_password,工具不支持,需要先改掉:
ALTER USER 'x'@'%' IDENTIFIED WITH mysql_native_password BY '...';
另一个相关 bug:在某些版本上重命名带外键的表会导致 server 挂掉,见 bugs.mysql.com bug 96145。
推荐上线流程
先跑 --dry-run 看一遍会做什么,确认无误后再执行。执行时建议把自动动作全部关掉,改成手工 swap:
pt-online-schema-change \
--alter "ADD COLUMN c1 INT" \
D=sakila,t=actor \
--execute \
--no-drop-old-table \
--no-drop-new-table \
--no-swap-tables \
--no-drop-triggers
之后自己确认新表数据,再手动完成 swap 和清理。
一句话区别:gh-ost 走 binlog 拿增量、不建触发器,因此不受「原表已有触发器就不能改」这条限制;pt-online-schema-change 依赖触发器,但流程更轻、对 PXC 之外的环境依赖更少。