割接前的 pt-table-checksum:报出的 DIFF 有一半是假阳性,先分清工具边界再修数据
割接前跑一遍 pt-table-checksum,输出几百行 DIFF,第一反应往往是「数据坏了」。先停一下:这些 DIFF 里相当一部分不是数据问题,而是校验工具本身的边界——目标端不是复制拓扑里的副本、两端时区不一致、collation 设置不同、浮点列精度差异,都会让同一行数据算出两个 checksum。先分清是工具边界还是数据本身,再决定修哪一侧。
它怎么工作:只在源端执行,靠复制流走到副本
pt-table-checksum 不做「两端各跑一遍再拉回来对」这件事。它在 replication source 上执行 checksum 查询:把每张表切成 chunk,每个 chunk 生成一条 REPLACE ... SELECT 写进 binlog,副本通过复制流重放同样的语句,得到自己那一侧的 checksum,工具再把结果收回来比对。
这意味着它依赖 statement 语义。工具启动时会在 source 上设置 binlog_format=STATEMENT,但受 MySQL 限制,副本不会跟随这个设置。所以如果拓扑中间有一层使用 ROW 格式的副本,而它本身又是下游副本的源,校验就过不去——那些下游副本收不到可重放的语句。工具会自动检查所有服务器上的 binlog_format,不满足就拒绝运行;确认拓扑能接受时可以用 --no-check-binlog-format 跳过这项检查。
工具假定 source 和所有副本的 schema、表结构完全一致。副本缺少被校验的 schema,或者表结构不同,复制会中断。修数据用 pt-table-sync,但那是修复工具,不是比对工具,用它之前要先确认差异是真的。
切块、哈希与自适应
按 chunk 切表。切分需要一个索引,优先主键或唯一索引;没有合适索引、行数又足够少的表,退化成整表一个 chunk。默认每个 chunk 的 checksum 查询目标耗时 0.5 秒,工具用指数衰减加权平均调整下一块的大小,服务器变忙时快速收缩,保持 chunk 稳定。
每个 chunk 先做 EXPLAIN,预估行数可能超限就跳过,灵敏度由 --chunk-size-limit 控制。小表单 chunk 时还会额外确认副本上这张表没有被放大,避免源端空表、副本巨表导致一条大查询拖长复制。
默认 --function=CRC32。CRC32 不是加密哈希,存在碰撞可能,但比 MD5/SHA1 快很多、CPU 开销更低。行数大、碰撞不能接受时换成 --function=MD5 或 SHA1,代价是两端计算量上来,同样的 0.5 秒目标会让 chunk 切得更碎。
自我保护与中断续跑
- session 级
innodb_lock_wait_timeout=1,让校验查询自己成为锁等待里先失败的一方,而不是拖住业务。 --max-load默认 25,并发查询数超过阈值就暂停,建议按服务器实际负载设。- 副本延迟太大就暂停等它追上;副本报错或复制停止也暂停等待。
- 查询被 kill 或锁等待超时不算致命错误,重试一次,再失败就跳到该表下一个 chunk;每张表只打印一次 warning。
- 完全停下后可以用
--resume从最后一张表的最后一个 chunk 继续;CTRL-C 会跑完当前 chunk 再退出。
结果落在 percona.checksums
每张表的所有 chunk 完成后,工具暂停等所有副本执行完 checksum 查询,再检查副本是否与源一致,打印一行结果。明细都在 percona.checksums 表里——工具会往这张表写数据,需要相应权限,而且这个写入本身也会随复制流走。
手工查差异:
SELECT db, tbl, SUM(this_cnt) AS total_rows, COUNT(*) AS chunks
FROM percona.checksums
WHERE (source_cnt <> this_cnt
OR source_crc <> this_crc
OR ISNULL(source_crc) <> ISNULL(this_crc))
GROUP BY db, tbl;
每行是某张表某个 chunk 的一条 checksum 记录:source_cnt/source_crc 是源端结果,this_cnt/this_crc 是副本侧算出来的。
什么时候这个工具根本用不上
割接场景里,目标端如果不是 MySQL 复制拓扑中的副本——云厂商 DTS/DMS 迁移、异构目标、通过中间件双写同步——pt-table-checksum 的整套机制就没有落脚点:没有副本会去重放那条 REPLACE ... SELECT。这种情况下只有两条路:用迁移服务自带的一致性校验,或者自己写分批 checksum 查询,在两端各跑一遍、在客户端比对结果。
方案文档里要把「校验方式」单独写成一节,明确是哪一种,不能默认「到时候跑一下 pt-table-checksum」。
报出来的不一致怎么分型
拿到 DIFF 列表,先别修,按下面的来源逐条排除:
- TIMESTAMP/DATETIME 与 time_zone:同一行值在不同会话时区下字符串表示不同,checksum 自然不同。两端
time_zone不一致是最常见的假阳性来源。 - 字符集与 collation:
utf8mb4_general_ci和utf8mb4_0900_ai_ci对大小写、重音的敏感度不同,比较和排序结果不同,checksum 也会不同。列级 collation 不一致在迁移里很常见。 - FLOAT/DOUBLE 精度:浮点表示和四舍五入差异,工具用
--float-precision控制参与 checksum 的小数位数,两端设置不同就会产生差异。 - BIT/JSON 列:BIT 列在不同客户端下的取值表示、JSON 的键顺序与空白,可能让字节层面不同,逻辑值却相等。
- 表名大小写:
lower_case_table_names设置不同,源端MyTable在副本上变成mytable,chunk 查询按名字定位会落到不同表或直接失败。 - 并行复制下的时间错位:校验查询在副本上重放时业务写入还在并行 apply,某个 chunk 的 checksum 与源端取数不在同一逻辑时刻,产生短暂差异,重跑一次通常就消失。
排除之后,结论落成三档:
- 可解释的假阳性:能对上上面某一条。记录现象和原因,复跑该表确认差异消失或稳定可解释,报告里标注「已排除」,不动数据。
- 确认真不一致:换一种方式交叉验证,比如按主键抽该 chunk 的全字段逐行比对,确认是数据本身不同。在目标侧修正——补数据、重放该批变更。不要用 pt-table-sync 反向写源库:割接前源库是基准,反向写会把差异带回基准侧。
- 无法判定:差异行数少但业务上重要,上面几条都解释不了。按主键分页、抽核心表全字段比对,把差异行落成明细表交业务确认。
命令示例
指定库表做校验,用 SHA1、限制负载、支持中断续跑:
pt-table-checksum \
--host=source.db.internal \
--user=checksum \
--password=*** \
--databases=order,payment \
--tables=order_info,order_item,pay_record \
--function=SHA1 \
--max-load='Threads_running=20' \
--chunk-time=0.5 \
--replicate=percona.checksums \
--resume
用 pt-table-sync 做只读检查,只打印修复语句、不执行:
pt-table-sync \
--host=source.db.internal \
--user=checksum \
--password=*** \
--databases=order \
--tables=order_info \
--replicate=percona.checksums \
--dry-run --print
不确定切块计划时,先让工具解释它会怎么做:
pt-table-checksum \
--host=source.db.internal \
--user=checksum \
--password=*** \
--databases=order \
--tables=order_info \
--explain
校验结果分型处理表
| 现象 | 判断 | 处置 |
|---|---|---|
差异集中在 TIMESTAMP/DATETIME 列,两端 time_zone 不同 | 可解释的假阳性 | 记录会话时区差异,统一时区后复跑该表 |
差异只出现在 collation 不同的字符列(general_ci vs 0900_ai_ci) | 可解释的假阳性 | 记录列级 collation 差异,复跑确认稳定 |
| FLOAT/DOUBLE 列 checksum 不同、涉及行数少 | 精度表示差异 | 调整 --float-precision 或按主键抽行比对 |
| 差异 chunk 在重跑后消失 | 并行复制下的取数错位 | 复跑确认,报告标注为瞬时差异 |
| 主键抽全字段比对确认某几行内容不同 | 真不一致 | 在目标侧补数据或重放变更,不用 pt-table-sync 反向写源库 |
| 差异无法用上述原因解释、涉及核心表 | 无法判定 | 按主键分页全字段比对,输出差异明细交业务确认 |
| 目标端不是 MySQL 复制拓扑里的副本 | 工具机制不适用 | 改用迁移服务自带校验,或两端分批 checksum 自行比对 |
参考信息
- Percona Toolkit pt-table-checksum 文档:https://docs.percona.com/percona-toolkit/pt-table-checksum.html
- Percona Toolkit pt-table-sync 文档:https://docs.percona.com/percona-toolkit/pt-table-sync.html