编程 pt-online-schema-change 在线改表:1 条触发器硬约束、4 道安全阀与手动 swap 流程

2026-10-16 00:03:58

pt-online-schema-change 在线改表:1 条触发器硬约束、4 道安全阀与手动 swap 流程

项目信息:

  • 官方文档:
  • GitHub:

它是怎么做到不锁表的

pt-online-schema-change 模拟了 MySQL 内部改表的方式,但操作对象是你想改的那张表的副本。所以原表不加锁,客户端可以继续读写。

流程大致是:

  1. 创建一张结构相同的空表;
  2. 按需要修改这张新表的结构;
  3. 从原表分块拷贝数据到新表。分块大小会动态调整,目标是让每块执行时间落在指定范围内(见 --chunk-time);
  4. 拷贝期间原表上的数据变更,通过工具在原表上创建的触发器同步到新表对应行;
  5. 拷贝完成后,用一次原子的 RENAME TABLE 同时重命名原表和新表;
  6. 默认还会把原表 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 之外的环境依赖更少。

推荐文章

程序员茄子在线接单