编程 割接前的 pt-table-checksum:报出的 DIFF 有一半是假阳性,先分清工具边界再修数据

2026-10-24 00:05:46

割接前的 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 与源端取数不在同一逻辑时刻,产生短暂差异,重跑一次通常就消失。

排除之后,结论落成三档:

  1. 可解释的假阳性:能对上上面某一条。记录现象和原因,复跑该表确认差异消失或稳定可解释,报告里标注「已排除」,不动数据。
  2. 确认真不一致:换一种方式交叉验证,比如按主键抽该 chunk 的全字段逐行比对,确认是数据本身不同。在目标侧修正——补数据、重放该批变更。不要用 pt-table-sync 反向写源库:割接前源库是基准,反向写会把差异带回基准侧。
  3. 无法判定:差异行数少但业务上重要,上面几条都解释不了。按主键分页、抽核心表全字段比对,把差异行落成明细表交业务确认。

命令示例

指定库表做校验,用 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

推荐文章

程序员茄子在线接单