pt-table-sync 修复侧:先 --dry-run --print,再决定改源还是改副本
工具文档:pt-table-sync。本文不讨论差异怎么算出来;比对侧看 pt-table-checksum。
pt-table-sync 会直接写数据。它成熟、久经生产,但用错方向时,代价是改坏源库。下面只讲修复侧:什么时候允许改、改在哪一端、哪些开关是红线。
一、默认只打印,--execute 才落笔
不带 --execute 时,工具只做分析并打印,不写库。但“安全上线”不止这一步,固定走两步:
pt-table-sync --dry-run --print \
--replicate percona.checksum \
h=source1 \
> /tmp/sync-preview.sql
--dry-run 表示只分析不执行,--print 打出真正会跑的 SQL。两者一起用,才看得到“将要发出什么语句”。确认语句形态和影响面符合预期后,再加 --execute。
--print 只打同步语句;如果想看选 chunk / nibble / row 的 SQL,需要 --print 配合两次 --verbose,输出量会很大。
文档的原始要求是:读文档、看 BUGS、非生产环境先测、生产先备份并验证备份。这不是客套话,因为 --execute 写下去就是真实数据。
二、--replicate 模式:只改有差异的 chunk
--replicate 指向 pt-table-checksum 用自身 --replicate 写出的结果表。工具读这张表,只修 checksum 判定为有差异的 chunk。这是最可控的路径——修什么由比对结果决定,而不是工具现场去猜。
pt-table-sync --execute --replicate percona.checksum h=source1
不带 --replicate 时,工具自己在两端找差异。此时命令行 DSN 的语义是严格的:第一个 DSN 是源,后面的是目的地;目的地可以有一个或多个,但必须与源相互独立,不能处于同一复制拓扑。工具一旦发现目的地是副本就会报错退出——它是直接往目的地写数据的,而直接写副本不安全。--no-check-replica 是绕过这个检查,不是修复这个拓扑错误。
--replicate 且不带 --sync-to-source 时,命令行只接受一个源 DSN,工具自动发现该源的所有副本并逐个修。这是“一次修全部副本”的唯一方式(--sync-to-source 一次只能指定一个副本)。上线前可以用 --explain-hosts 看工具如何解释这些 DSN。
另外,--sync-to-source 和 --replicate 都要求 statement-based replication,工具会在自己的会话里设置 binlog_format=STATEMENT,执行用户需要 SUPER 权限。
三、--sync-to-source:只接受一个副本 DSN
pt-table-sync --execute --sync-to-source replica1
命令行上只能有这一个 DSN,且它是副本。工具自动找到它的源,在源上执行修改,靠复制流把变更带回副本。这是把副本拉回与源一致的常规安全路径——副本上的数据通常正是问题所在,直接改副本等于把问题又写回去一遍。
副作用必须提前说清楚:同一源上挂着的其它副本,同样会通过复制收到这批变更。理论上这些变更对它们是把值设成当前值的 no-op,但前提是拓扑干净、没有额外的复制过滤或触发器。这一点需在预发验证。
四、无主键 / 唯一键:改源不安全
REPLACE 只有在表上有唯一索引时才成立。没有唯一索引时,REPLACE 退化为普通 INSERT,改源会把重复行顺着复制流发给所有副本。
这类表没有别的选择,只能在副本上改,所以必须显式加上 --no-check-replica 表示知情:
pt-table-sync --execute --no-check-replica --sync-to-source replica1
source-source 下的无唯一键表更麻烦:改动必须写在目的端,此时必须加 --no-bin-log,否则改动会通过反向复制流回另一端,把另一侧的数据一起改掉。
文档的建议是:无主键 / 唯一键的表,最好换一种方式同步数据。
五、source-source:正确写法与错误写法的区别
已知 source2 上的 db.tbl 有问题:
pt-table-sync --execute --sync-to-source h=source2,D=db,t=tbl
含义是把 source2 当作副本,连到它的源 source1,在 source1 上改,改动经复制流回到 source2。
下面这条不要用:
# source-source 下不要这样写
pt-table-sync --execute h=source1,D=db,t=tbl source2
它直接在 source2 上改,改动会顺着复制流向 source1,把 source1 的数据冲掉。
六、唯一键冲突:UPDATE 被自动改写成 DELETE + REPLACE
假设表上 a 是主键、b 上有唯一索引:
-- 源 副本
-- a b a b
-- 1 2 1 1
-- 2 1 2 2
这种数据用 UPDATE 无论怎么改都会撞唯一键。工具在第一次索引冲突之后会自动把语句改写成 DELETE + REPLACE,不需要人工干预。
代价是 binlog 里会变成两条语句而不是一条。下游如果有基于 binlog 的审计、增量同步或触发器链路,需要按两条来对待。这一点需在预发验证。
七、外键级联与 chunk 控制
表上带 ON DELETE / ON UPDATE 外键时,一次删除或更新可能级联改动子表,而子表并不在你的同步范围内,数据就被顺带改了。用 --check-child-tables 让工具在同步前拦截。
大表上如果 chunk 相关列没有索引,行选择会退化。用 --chunk-index 指定索引,用 --chunk-size 控制每个 chunk 的行数:
pt-table-sync --execute --replicate percona.checksum \
--check-child-tables \
--chunk-index=PRIMARY \
--chunk-size=1000 \
h=source1
超大 chunk 或 MyISAM 表还要考虑 --no-transaction:默认每个 chunk 包在事务里,MyISAM 下事务没有意义。
八、退出码 2 表示“发现并修复了差异”,不是失败
0:无差异2:发现差异(带--execute时已修复)- 其它值:错误
脚本里必须把 2 当成功分支处理。否则每次真正修完东西都会触发告警,最后所有人都去忽略这个告警——等到真出错时也没人看。
--verbose 会按表打一行结果,包含 DELETE / REPLACE / INSERT / UPDATE 条数、算法、起止时间、退出码,可以拿来核对上面的判断。
九、场景对照表
| 场景 | 是否安全 | 正确命令 |
|---|---|---|
| 只预览,不写库 | 安全 | pt-table-sync --dry-run --print --sync-to-source replica1 |
| 有 checksum 结果,修该源的全部副本 | 安全 | pt-table-sync --execute --replicate percona.checksum h=source1 |
| 有 checksum 结果,只修一个副本 | 安全 | pt-table-sync --execute --replicate percona.checksum --sync-to-source replica1 |
| 两个独立实例之间搬数据 | 安全(目的地必须独立拓扑) | pt-table-sync --execute h=host1,D=db,t=tbl h=host2 |
| 目的地是副本 | 不安全,工具直接退出 | 改用 --sync-to-source |
| 无主键 / 唯一键的表 | 只能改副本 | pt-table-sync --execute --no-check-replica --sync-to-source replica1 |
| source-source 下的无唯一键表 | 必须加 --no-bin-log | pt-table-sync --execute --no-bin-log h=...,D=db,t=tbl h=... |
| source-source,已知 source2 数据有问题 | 安全 | pt-table-sync --execute --sync-to-source h=source2,D=db,t=tbl |
source-source 写成 h=source1,... source2 | 不安全,会冲掉 source1 数据 | 不要使用 |
执行顺序归纳成一条线:先 --dry-run --print 看语句,再对一个副本小范围 --execute,确认 binlog 形态与子表影响,最后才用 --replicate 批量过全部副本。有外键先加 --check-child-tables,无唯一键先确认自己接受“只改副本”这个前提。