Skip to content

pt-table-checksum

在主库上执行分块校验查询,让它随复制流到从库重新执行,从而在线发现主从数据不一致。

语法

bash
pt-table-checksum [OPTIONS] [DSN]

pt-table-checksum 通过在复制源(主库)上执行校验查询来做在线一致性检查——与主库数据不一致的从库上,同一条查询会算出不同的结果。可选的 DSN 用来指定主库主机。只要发现差异,或出现任何警告与错误,工具的退出状态就非零。

连接 localhost 上的主库,校验每一张表,并在检测到的每个从库上报告结果:

bash
pt-table-checksum

本工具专注于高效地发现数据差异;差异找出来之后,用 pt-table-sync 去修复。

使用前

Percona Toolkit 久经生产验证,但所有数据库工具都可能对系统和数据库服务器造成风险。使用前请:阅读工具文档、查看已知问题、先在非生产服务器上测试、备份生产服务器并验证备份可用。另请阅读下文"限制与注意事项"。

用法示例

以下命令假定已通过选项文件或本机 socket 配置好 MySQL 连接;远程主库用 h=主库IP,P=端口,u=用户,p=密码 指定。发现差异后请用 pt-table-sync 修复。

场景:校验整个实例

连接本机主库,校验所有库表,并在发现的每个从库上报告差异:

bash
pt-table-checksum

场景:只校验某个业务库

只校验 appdb 一个库,缩短校验时间:

bash
pt-table-checksum --databases appdb

场景:只校验某张表

怀疑某张表主从不一致,只针对它校验:

bash
pt-table-checksum --tables appdb.orders

场景:只校验最近新增的数据

对只增不改的表(如流水、日志表),每天只查前一天的新增行,避免反复重查历史数据:

bash
pt-table-checksum --databases appdb --where "ts > CURRENT_DATE - INTERVAL 1 DAY"

场景:定时校验 + 事后单独取差异报告

第一步放进 cron 静默执行校验(只写结果、不比对),第二步需要时再单独比对并打印差异:

bash
# 第一步:cron 定时执行,只写校验结果
pt-table-checksum --databases appdb --no-replicate-check
bash
# 第二步:随时取差异报告
pt-table-checksum --replicate-check-only

场景:限流防拖库

控制从库延迟与服务器负载,避免校验把主从压垮:

bash
pt-table-checksum --databases appdb --max-lag 2s --max-load Threads_running=30

功能说明

pt-table-checksum 的设计目标是绝大多数场景下默认行为就是对的。拿不准时用 --explain 看它打算怎么校验一张表。

与老版本不同,现在的 pt-table-checksum 只做一件事,不再支持多种校验技术:它只在一台服务器上执行校验查询,靠复制把这些查询带到从库上重新执行。需要老行为的话只能用 Percona Toolkit 1.0。

工具连接到指定的服务器,找出匹配过滤条件的库和表,然后一次只处理一张表,因此不会先累积大量内存或做很多准备工作才开始校验。这让它可以用在极大的服务器上——官方用它跑过有几十万个库和表、上万亿行的服务器,服务器再大,表现同样稳定。

分块校验(chunk)

能处理超大表的关键在于:工具把每张表切成一段段行(chunk),每个块用一条 REPLACE..SELECT 查询算出校验和。分块而不是整表一条大查询,目的是让校验过程不打扰线上业务、不造成过多复制延迟和负载——所以每个块的目标执行时间默认只有 0.5 秒。

块大小是自适应的。工具持续跟踪服务器执行这些查询的速度,并随着对服务器性能了解的加深不断调整块大小。它使用指数衰减加权平均,既让块大小保持稳定,又能在校验期间服务器性能变化时快速响应。比如遇到流量高峰或后台任务导致服务器负载飙升,工具会迅速给自己限流。

分块用的是 Percona Toolkit 里称为 "nibbling" 的技术,pt-archiver 用的也是同一套。老版本的分块算法已被移除,因为它们算出的块大小不可预测,在很多表上表现不好。分块的唯一要求是表上有某种索引,最好是主键或唯一索引;如果没有索引而表的行数又足够少,工具会把整表当作一个块来校验。

块大小的自适应由 --chunk-time(目标耗时)和 --chunk-size(固定行数)控制:不显式设 --chunk-size 时,它的默认值 1000 只作为起点,之后工具就不再理会它;一旦显式指定,自适应就关闭,所有块都尽量取指定的行数。把 --chunk-time 设为 0 也能关闭自适应。

这里有个微妙之处:分块索引不唯一时,块可能比预期大。例如某个索引值重复了 10000 次,就写不出只匹配 1000 行的 WHERE 子句,那个块至少 10000 行大——这种块通常会因为 --chunk-size-limit 被跳过。另外块设得太小会让工具明显变慢,因为 --[no]check-plan 的准备工作开销占比会变高。

结果表 percona.checksums

校验结果写入 --replicate 指定的表,默认是 percona.checksums,表中每一行是服务器上某张表的某一个块的校验和。表结构必须是这样(默认 --create-replicate-table 为 yes,库和表不存在时自动创建):

sql
CREATE TABLE checksums (
  db             CHAR(64)     NOT NULL,
  tbl            CHAR(64)     NOT NULL,
  chunk          INT          NOT NULL,
  chunk_time     FLOAT            NULL,
  chunk_index    VARCHAR(200)     NULL,
  lower_boundary TEXT             NULL,
  upper_boundary TEXT             NULL,
  this_crc       CHAR(40)     NOT NULL,
  this_cnt       INT          NOT NULL,
  source_crc     CHAR(40)         NULL,
  source_cnt     INT              NULL,
  ts             TIMESTAMP    NOT NULL DEFAULT CURRENT_TIMESTAMP ON UPDATE CURRENT_TIMESTAMP,
  PRIMARY KEY (db, tbl, chunk),
  INDEX ts_db_tbl (ts, db, tbl)
) ENGINE=InnoDB DEFAULT CHARSET=utf8;

各列含义:

  • dbtbl:被校验的库名与表名。
  • chunk:该表内的块编号;(db, tbl, chunk) 构成主键,一个块一行。
  • chunk_time:该块的耗时(FLOAT,可空)。
  • chunk_index:用于分块的索引名。
  • lower_boundary / upper_boundary:界定这个块的索引值下界与上界。这两列的数据类型也可以是 BLOB,见 --binary-index
  • this_crc / this_cnt本机上这个块的校验和与行数。同一条 REPLACE..SELECT 在主库算出主库的数据,随 binlog 到从库后在从库上重新执行,于是从库那一行记的就是从库自己数据算出的结果。
  • source_crc / source_cnt:主库上这个块的校验和与行数。把它和 this_* 对比就能判断这个块是否有差异。
  • ts:时间戳。

也可以手工查询结果表拿报告。下面这条查询列出所有存在差异的库和表,以及可能受影响的块数与行数汇总:

sql
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;

结果表的存储引擎

务必给结果表选合适的存储引擎。如果校验的是 InnoDB 表,而结果表用了 MyISAM,一次死锁就会弄坏复制:校验语句里混用了事务表和非事务表,即使出错也会被写进 binlog,在从库上重放时不会死锁,于是以"主从错误不一致"的形式中断复制。这不是 pt-table-checksum 的问题,而是 MySQL 复制的行为。

结果表自身永远不会被校验(工具会自动把它加进 --ignore-tables)。

pt-table-checksum 2.0 与 pt-table-sync 1.0 不兼容。有些情况下这问题不大:给结果表加一个 boundaries 列、再用手工拼出的 WHERE 子句填上它,可能就足以让 pt-table-sync 1.0 与 pt-table-checksum 2.0 协同工作。假设主键是名为 id 的整数列,可以试试:

sql
ALTER TABLE checksums ADD boundaries VARCHAR(500);
UPDATE checksums
SET boundaries = COALESCE(CONCAT('id BETWEEN ', lower_boundary,
  ' AND ', upper_boundary), '1=1');

校验函数与浮点差异

需要注意,pt-table-checksum 默认使用 CRC32 校验和。CRC32 不是密码学算法,因此容易发生碰撞;但另一方面它比 MD5 和 SHA1 更快、更省 CPU。--function 可以换成 MD5、SHA1,或自己编译安装的 UDF(如 Percona Server 附带的 FNV1A_64()MURMUR_HASH())。

相关阅读:Percona Toolkit UDFsHow to avoid hash collisions when using MySQL's CRC32 function

不同 MySQL 版本和硬件上同一个浮点值的字符串表示可能不同,从而产生假差异,用 --float-precision 指定小数位数四舍五入可以避免。MySQL 5.0 及以后保留 VARCHAR 尾部空格而更早版本会去掉,跨版本比较时用 --trim 给 VARCHAR 加 TRIM()

发现从库与沿拓扑传播

工具找不到的从库,上面的差异也就发现不了,所以它会自动检测从库并连上去。自动检测失败时用 --recursion-method 给它提示,--recurse 控制递归的层数(默认无限)。如果没找到任何从库、而方法又不是 none,工具会打印警告并返回非零退出状态。

--recursion-method 可选的几种方式:

METHOD       USES
===========  =================================================
processlist  SHOW PROCESSLIST
hosts        SHOW REPLICAS (SHOW SLAVE HOSTS before MySQL 8.1)
cluster      SHOW STATUS LIKE 'wsrep_incoming_addresses'
dsn=DSN      DSNs from a table
none         Do not find replicas
  • processlist 是默认方式,因为 SHOW REPLICAS 不可靠;但如果服务器用的是非标准端口(不是 3306),hosts 会成为默认,因为这种情况下它工作得更好。
  • hosts 要求从库配置了 report_hostreport_port 等参数。
  • cluster 需要基于 Galera 23.7.3 或更新版本的集群(如 Percona XtraDB Cluster 5.5.29 及以上),通过 SHOW STATUS LIKE 'wsrep_incoming_addresses' 自动发现集群节点。可以把 clusterprocesslisthosts 组合起来同时发现节点和从库,但这个能力是实验性的。
  • dsn 比较特殊:它不自动发现,而是指定一张存放从库 DSN 的表,工具只连接表里列出的这些从库。当从库与主库的用户名/密码不同,或者你想阻止工具连某些从库时,这种方式最合适。用法形如 --recursion-method dsn=h=host,D=percona,t=dsns,给出的 DSN 必须带 Dt 两部分,或者带库名限定的 t 部分。DSN 表结构必须是:
sql
CREATE TABLE `dsns` (
  `id` int(11) NOT NULL AUTO_INCREMENT,
  `parent_id` int(11) DEFAULT NULL,
  `dsn` varchar(255) NOT NULL,
  PRIMARY KEY (`id`)
);

DSN 按 id 排序,除此之外 idparent_id 都被忽略。dsn 列写的就是命令行里那种 DSN,例如 "h=replica_host,u=repl_user,p=repl_pass"

  • none 让工具忽略所有从库和集群节点。不推荐,因为这实际上关掉了下文的从库检查,也就发现不了任何差异;只在你只需要往主库或单个集群节点写校验和时才有用。更安全的替代是 --no-replicate-check:工具照样发现从库和节点、照样做从库检查,只是不去比对差异。

差异比对:--replicate-check

一张表的所有块校验完之后,工具会暂停下来等所有已发现的从库执行完这些校验查询;等完了,就检查各从库的数据是否与主库相同,然后打印一行结果。这个比对由 --[no]replicate-check(默认开启)完成——在所有检测到的从库上执行一条简单的 SELECT,把从库的校验结果与主库的校验结果对比,差异数量报在输出的 DIFFS 列。

--replicate-check-only 则相反:完全不执行校验查询,只检查之前校验留下的差异然后退出。适合把 pt-table-checksum 静默放在 cron 里跑、之后再单独取一份报告的场景,比如实现 Nagios 检查。用 --resume 续跑时可能出现假差异,把 --replicate-check-retries 设成 2 或更大可以缓解——只有差异在多次检查后仍然存在才算真差异。

限流与安全阀

除自适应块大小之外,工具还有一系列保障机制,确保不干扰任何服务器(包括从库)的运行:

  • 复制延迟--max-lag(默认 1s)在每个块之后用 Seconds_Behind_Source 检查所有已连接从库的延迟,超过阈值就睡 --check-interval 秒再查一遍。工具会无限等待延迟消退;从库被停掉时也无限等到它启动为止。等待期间打印进度报告;从库停了会立即打印一次,之后按进度间隔继续打印。用 --check-replica-lag 可把延迟监控限定到某一台从库——某些从库是故意做成延迟从库时尤其有用,可以指定一台正常从库来监控;--skip-check-replica-lag 则可排除指定 DSN。--fail-on-stopped-replication 让"复制已停止"直接报错退出而不是等待。
  • 服务器负载--max-load 在每个块之后检查 SHOW GLOBAL STATUS,任一状态变量超过阈值就暂停,默认 Threads_running=25。校验查询若过于打扰业务、或造成锁等待,其他查询就会阻塞排队,通常表现为 Threads_running 上升。这个选项只是给服务器一个从排队中恢复的机会,并不能阻止排队;真发现排队,最好的办法是减小 --chunk-time
  • 块不能过大:工具对每个块执行 EXPLAIN,跳过估算行数可能超标的块,灵敏度用 --chunk-size-limit(默认 2.0,即两倍)调节。如果一张表因为行数少而要用单个块校验,工具还会额外确认这张表在从库上没有超大——避免"表在主库是空的、在从库上却很大,被一条大查询整表校验,造成极长复制延迟"这种情况。
  • 锁等待:工具把会话级 innodb_lock_wait_timeout 设为 1 秒,这样一旦发生锁等待,牺牲者是它自己,而不是让别的查询超时。
  • 执行计划--[no]check-plan(默认开启)在执行那些"本该只访问少量数据、但计划选错就可能扫很多行"的查询前先跑 EXPLAIN,包括确定块边界的查询和块校验查询本身;判断计划不好就跳过这个块。
  • 复制过滤器:工具会主动排查常见的麻烦源头,比如复制过滤器,不强制就拒绝运行。复制过滤器很危险,因为工具执行的查询可能与它们冲突并导致复制中断。
  • 暂停文件--pause-file 指定的文件存在期间暂停执行。

从库检查

默认情况下工具会尝试找到并连接主库下所有从库,这个自动过程叫"从库递归"(replica recursion),由 --recursion-method--recurse 控制。工具在所有从库上执行以下检查:

  • --[no]check-replication-filters:在所有从库上检查复制过滤器,因为它们会让校验过程复杂化甚至中断。默认发现任何过滤器就退出,可用 --no-check-replication-filters 关闭这项检查。
  • --replicate:检查结果表在所有从库上都存在,否则主库对该表的更新复制到没有这张表的从库时会弄坏复制。这项检查无法关闭,工具会无限等到该表在所有从库上都存在,等待期间打印 --progress 消息。
  • 单块表的大小:如果一张表在主库上可以单个块校验完,工具会检查它在所有从库上的大小是否小于 --chunk-size × --chunk-size-limit,以防主库上表很小、从库上却大得多,单块校验直接把从库压垮。还有一种少见情况:从库上表的大小接近 --chunk-size × --chunk-size-limit 时,这张表更容易被无谓地跳过——因为表大小是估算值,两者接近时这项检查对估算误差比对真实差异更敏感。把 --chunk-size-limit 设大一点可以避免。这项检查无法关闭,另见 --replica-skip-tolerance
  • 延迟:每个块之后检查所有从库(或 --check-replica-lag 指定的那一台)的延迟,避免用校验数据压垮从库。这项检查无法关闭,但可以只指定一台从库来查;如果指定的是最快的那台,就能避免工具在延迟上等太久。
  • 校验块:一张表校验完时,工具等最后一个校验块复制到所有从库,才能做 --[no]replicate-check。指定 --no-replicate-check 会同时关掉这项等待和差异的即时上报,因此需要第二次运行工具、带 --replicate-check-only 才能找出并打印差异。

容错、中断与续跑

校验通常是低优先级任务,应该给服务器上的其他工作让路;但一个需要人不停重启的工具很难用,所以 pt-table-checksum 对错误非常有韧性:

  • DBA 因为任何原因 kill 掉工具的查询都不算致命错误——很多人会用 pt-kill 杀掉长时间运行的校验查询。工具会重试被杀掉的查询一次,再失败就跳到该表的下一个块。锁等待超时的处理方式相同(重试次数见 --retries)。这类错误只在每张表上打印一次警告。
  • 与任何服务器的连接断开时,工具会尝试重连并继续工作。
  • 遇到导致完全停止的情况时,用 --resume 很容易续跑:它从上次处理的最后一张表的最后一个块开始。也可以用 CTRL-C 安全地停止工具——它会做完当前正在处理的块再退出,之后照常续跑。
  • --run-time--resume 配合,可以在限定时间内校验尽可能多的表,下次运行从上次停的地方接着来。
  • 注意 --[no]empty-replicate-table(默认开启)只删除即将校验的那张表的旧校验记录,不会清空整张结果表;所以若校验中途停止且原本有数据,那些没被校验到的表的记录仍然留着。从上次续跑时,续跑起点那张表的记录也不会被清空。要清空整张结果表得手工 TRUNCATE TABLE,或使用 --truncate-replicate-table

进度报告

工具在耗时操作期间打印进度指示:每张表校验时打印一次进度(按表的估算行数计算),等待复制追赶时、以及等待检查从库与主库差异时也会打印。用 --quiet 可以让输出不那么啰嗦。

限制与注意事项

使用基于行的复制的从库:pt-table-checksum 要求基于语句的复制(statement-based replication),它会在主库上设置 binlog_format=STATEMENT,但受 MySQL 限制,从库不认这个改动。因此,如果链路中某个从库使用基于行的复制、而它又是更下游从库的主库,校验和就无法再往下传播。工具会自动检查所有服务器的 binlog_format,见 --[no]check-binlog-format(Bug 899415)。

schema 与表结构差异:工具假定主库和所有从库上的 schema 与表完全相同。举例来说,如果主库上有一个被校验的 schema 而从库上没有,或者从库上某张表的结构与主库不同,复制就会中断。

RocksDB:由于 RocksDB 引擎的限制——不支持 binlog_format=STATEMENT,以及它处理间隙锁(Gap lock)的方式——pt-table-checksum 会跳过使用 RocksDB 引擎的表。详见 MyRocks limitations

Percona XtraDB Cluster

pt-table-checksum 支持 Percona XtraDB Cluster(PXC)5.5.28-23.7 及更新版本。PXC 可能的部署形态很多(还能与普通复制混用),因此只有下面列出的几种形态受支持、确认可用,其他形态(如集群到集群)不受支持,很可能不工作。

除特别说明外,下列所有受支持形态都要求用 --recursion-methoddsn 方式指定集群节点;另外,对集群节点不做延迟检查

单个集群:最简单的 PXC 形态,所有服务器都是集群节点,没有普通从库。只要所有节点都写在 DSN 表里,就可以在任意节点上运行工具,其他任意节点上的差异都能被发现。所有节点必须属于同一个集群(wsrep_cluster_name 值相同),否则工具报错退出。虽然技术上可以让不同集群同名,但不应这么做,也不受支持——这条适用于所有受支持形态。

单个集群 + 普通从库:集群节点也可以作为普通的复制源,向普通从库复制。但工具只有在从库的"源节点"上运行时才能发现该从库的差异。例如这样的拓扑:

node1 <-> node2 <-> node3
  |         |
  |         +-> replica3
  +-> replica2

在 node3 上运行可以发现 replica3 的差异,但要发现 replica2 的差异必须在 node2 上再跑一次。在 node1 上运行则两个从库的差异都发现不了。目前工具不会检测这种形态、也不会对无法检查的从库告警(比如在 node3 上运行时的 replica2)。这种形态下的从库仍然受 --[no]check-binlog-format 约束。

主库 → 单个集群:普通主库可以把整个集群当作一个逻辑从库来复制:

source -> node1 <-> node2 <-> node3

工具支持这种形态,但只能在主库上运行,且要求集群内所有节点都与主库的"直连从库"(本例中的 node1)一致。举例说,所有节点第 1 行的值都是 "foo" 而主库是 "bar",这个差异能被发现;只有 node1 有这个差异也能被发现;但只有 node2 或 node3 有差异就发现不了。所以这种形态用来检查主库与集群作为整体是否一致。这种形态下工具能在主库上自动识别出直连从库(node1),因此不必用 dsn 方式的 --recursion-method——node1 代表整个集群,这也正是其他节点必须与它一致的原因。工具检测到这种形态时会告警,提醒你它只在上述用法下有效;这些告警不影响退出状态,只是帮助避免误判的提示。

插件

--plugin 指定的文件必须定义一个名为 pt_table_checksum_plugin 的类(package),并带有 new() 子程序。工具会创建这个类的实例,并调用它定义的所有钩子。钩子不是必需的,但没有钩子的插件也没什么用。

按调用顺序,已定义的钩子会被依次调用:

init
before_replicate_check
after_replicate_check
get_replica_lag
before_checksum_table
after_checksum_table

每个钩子接收的参数不同。想知道某个钩子接收哪些参数,在工具源码里搜索钩子名,例如:

perl
# --plugin hook
if ( $plugin && $plugin->can('init') ) {
  $plugin->init(
  replicas         => $replicas,
  replica_lag_cxns => $replica_lag_cxns,
  repl_table       => $repl_table,
  );
}

注释 # --plugin hook 出现在每一处钩子调用之前。

输出

工具打印表格形式的结果,一张表一行:

  TS ERRORS  DIFFS  ROWS  DIFF_ROWS CHUNKS SKIPPED    TIME TABLE
10-20T08:36:50      0      0   200      0       1       0   0.005 db1.tbl1
10-20T08:36:50      0      0   603      3       7       0   0.035 db1.tbl2
10-20T08:36:50      0      0    16      0       1       0   0.003 db2.tbl3
10-20T08:36:50      0      0   600      0       6       0   0.024 db2.tbl4

错误、警告和进度报告打印到标准错误,另见 --quiet。每张表的结果在该表校验完成时打印。各列含义:

  • TS:工具校验完这张表的时间戳(不含年份)。
  • ERRORS:校验这张表期间发生的错误与警告数量。表还在处理中时,错误和警告就已打印到标准错误。
  • DIFFS:在一个或多个从库上与主库不同的块数量。指定 --no-replicate-check 时这一列永远是 0;指定 --replicate-check-only 时只打印有差异的表。
  • ROWS:从这张表里选出并校验的行数。用了 --where 时它可能与表的实际行数不同。
  • DIFF_ROWS:单个块的最大差异行数。若一个块有 2 行不同、另一个块有 3 行不同,这个值是 3。
  • CHUNKS:这张表被切成的块数。
  • SKIPPED:因为下列问题之一而被跳过的块数:
* MySQL not using the --chunk-index
* MySQL not using the full chunk index (--[no]check-plan)
* Chunk size is greater than --chunk-size * --chunk-size-limit
* Lock wait timeout exceeded (--retries)
* Checksum query killed (--retries)

即:MySQL 没用 --chunk-index;MySQL 没用完整的分块索引(--[no]check-plan);块大小超过 --chunk-size × --chunk-size-limit;锁等待超时(--retries);校验查询被 kill(--retries)。从 pt-table-checksum 2.2.5 起,跳过块会导致非零退出状态。

  • TIME:校验这张表消耗的时间。
  • TABLE:被校验的库和表。

指定 --replicate-check-only 时只打印检测到的从库上的校验差异,输出格式不同:一个从库一段,一处差异一行,值以空格分隔:

Differences on h=127.0.0.1,P=12346
TABLE CHUNK CNT_DIFF CRC_DIFF CHUNK_INDEX LOWER_BOUNDARY UPPER_BOUNDARY
db1.tbl1 1 0 1 PRIMARY 1 100
db1.tbl1 6 0 1 PRIMARY 501 600

Differences on h=127.0.0.1,P=12347
TABLE CHUNK CNT_DIFF CRC_DIFF CHUNK_INDEX LOWER_BOUNDARY UPPER_BOUNDARY
db1.tbl1 1 0 1 PRIMARY 1 100
db2.tbl2 9 5 0 PRIMARY 101 200

每段的第一行标明有差异的从库,上例中有两个:h=127.0.0.1,P=12346h=127.0.0.1,P=12347。各列含义:

  • TABLE:与主库不同的库和表。
  • CHUNK:与主库不同的块编号。
  • CNT_DIFF:从库上该块的行数减去主库上该块的行数。
  • CRC_DIFF:从库上该块的 CRC 与主库不同则为 1,否则为 0。
  • CHUNK_INDEX:用于分块的索引。
  • LOWER_BOUNDARY:界定该块下界的索引值。
  • UPPER_BOUNDARY:界定该块上界的索引值。

退出状态

pt-table-checksum 有三类退出状态:0、255,以及作为位掩码(bitmask)的其他任意值。

  • 0:没有错误、警告、校验差异,也没有跳过任何块或表。
  • 255:致命错误,即工具死掉或崩溃,错误打印到 STDERR。
  • 其他值:作为位掩码,各标志位如下:
FLAG              BIT VALUE  MEANING
================  =========  ==========================================
ERROR                     1  A non-fatal error occurred
ALREADY_RUNNING           2  --pid file exists and the PID is running
CAUGHT_SIGNAL             4  Caught SIGHUP, SIGINT, SIGPIPE, or SIGTERM
NO_REPLICAS_FOUND         8  No replicas or cluster nodes were found
TABLE_DIFF               16  At least one diff was found
SKIP_CHUNK               32  At least one chunk was skipped
SKIP_TABLE               64  At least one table was skipped
REPLICATION_STOPPED     128  Replica is down or stopped

各标志位含义依次为:1 发生了非致命错误;2 --pid 文件已存在且其中的 PID 正在运行;4 捕获到 SIGHUP、SIGINT、SIGPIPE 或 SIGTERM;8 没找到任何从库或集群节点;16 至少发现一处差异;32 至少跳过一个块;64 至少跳过一张表;128 从库宕机或已停止。

只要有任一标志位置位,退出状态就非零。用按位与判断某个标志,例如 $exit_status & 16 为真就说明至少发现了一处差异。从 pt-table-checksum 2.2.5 起跳过块会导致非零退出状态,所以退出状态 0 或 32 等同于老版本"有跳过块但退出状态为 0"的情形。

选项

选项说明
--ask-pass连接 MySQL 时交互式询问密码
--binary-index改变 --create-replicate-table 的行为,把结果表的 lower_boundary、upper_boundary 列建成 BLOB 类型。当被校验表的键含二进制类型、或使用非标准字符集导致校验困难时有用。参见 --replicate
--[no]buffer-stdout默认开启 STDOUT 缓冲;使用 tee、kubectl logs 等后处理工具想看到实时进度时可关闭
--channel类型:string。使用复制通道(replication channel)时指定通道名。例如一个从库通过通道 chan_source_a、chan_source_b 同时连接两个主库,SHOW REPLICA STATUS 会返回 2 行,工具无法判断哪个才是正确的主库,此时用 --channel=chan_source_a 指定 SHOW REPLICA STATUS 使用的通道名
-A, --charset类型:string。默认字符集(utf8 时设置 binmode/mysql_enable_utf8 并执行 SET NAMES UTF8
--[no]check-binlog-format默认:yes。检查所有服务器上的 binlog_format 是否相同。见"限制与注意事项"中基于行的复制一节
--check-interval类型:time;默认:1。两次 --max-lag 检查之间的睡眠时间
--[no]check-plan默认:yes。检查查询执行计划是否安全:对确定块边界的查询和块校验查询先执行 EXPLAIN,判断计划不好就跳过该块。判定用了几个启发式规则——一是 MySQL 是否打算使用期望的索引,选了别的索引就视为不安全;二是看 EXPLAIN 的 key_len 列,工具记住见过的最大 key_len,若 MySQL 报告只用更短的索引前缀就跳过该块(相当于跳过执行计划比其他块更差的块)。每张表首次因坏计划跳过块时打印一次警告,后续静默跳过,数量见输出的 SKIPPED 列。这项检查给每张表和每个块增加了额外准备工作,虽然对 MySQL 不算打扰,但会增加与服务器的往返次数从而消耗时间;块越小开销占比越大,因此不建议把块设得太小
--check-replica-lag类型:string;组:Throttle。暂停校验直到这台从库的延迟小于 --max-lag。值是一个 DSN,会继承主库主机和连接选项(--port--user 等)的属性。默认工具监控所有已连接从库的延迟,本选项把监控限定到指定的这一台——某些从库是故意做成延迟从库(delayed replication)时很有用,可以指定一台正常从库来监控
--[no]check-replica-tables默认:yes;组:Safety。检查从库上的表存在、且具备全部校验列(--columns)。从库缺表或缺列会让工具在检查差异时弄坏复制。只有在清楚风险、并确认所有从库上所有表都与主库完全一致时才关闭
--[no]check-replication-filters默认:yes;组:Safety。任何从库上设置了复制过滤器就不做校验。工具会查找 binlog_ignore_dbreplicate_do_db 这类过滤复制的服务器选项,发现任何一个就报错中止。从库配了过滤选项时要特别小心,别去校验只在主库存在、从库不存在的库或表:对这类表的改动在从库上通常会因过滤被跳过,但校验查询修改的是存放校验和的那张表,而不是被校验的表,所以这些查询会在从库上执行,一旦被校验的库表在从库上不存在,就会让复制失败。有复制过滤时不可能保证校验查询不弄坏复制(或干脆复制不过去);确信执行校验查询没问题时可以取反本选项关闭检查。另见 --replicate-database
--check-slave-lag类型:string。已废弃,将在未来版本移除,请改用 --check-replica-lag
--[no]check-slave-tables默认:yes;组:Safety。已废弃,将在未来版本移除,请改用 --[no]check-replica-tables
--chunk-index类型:string。优先用这个索引分块。默认工具自己挑最合适的索引,本选项让你指定偏好;索引不存在时回退到默认行为。工具会把该索引以 FORCE INDEX 子句加进校验 SQL 语句,因此要小心——选错索引会导致糟糕的性能。这个选项更适合只校验单张表而非整台服务器时使用
--chunk-index-columns类型:int。只使用 --chunk-index 最左边的这么多列。仅对复合索引有效。适用于 MySQL 查询优化器的 bug 导致它扫描大范围行、而不是用索引精确定位起止点的情况(这种问题有时出现在列数很多、比如 4 列以上的索引上)。发生时工具可能打印与 --[no]check-plan 相关的警告,让工具只用索引前 N 列是某些情况下的变通办法
--chunk-size类型:size;默认:1000。每条校验查询选取的行数,允许 k、M、G 后缀。多数情况下不该用它,优先用 --chunk-time。它会覆盖"动态调整块大小以使每块正好跑 --chunk-time 秒"的默认行为:不显式设置时它的默认值只作为起点,之后工具就忽略这个值;一旦显式设置就关闭动态调整,所有块都尽量取指定行数。注意分块索引不唯一时块可能大于预期(如索引里某个值有 10000 行,就写不出只匹配 1000 行的 WHERE),这种块很可能因 --chunk-size-limit 被跳过。块设得太小会让工具慢很多,部分原因是 --[no]check-plan 的准备开销
--chunk-size-limit类型:float;默认:2.0;组:Safety。不校验比目标块大小超出这么多倍的块。表没有唯一索引时块大小可能不准,本选项规定可容忍的不准上限:工具用 EXPLAIN 估算块内行数,超过目标块大小乘以该倍数(默认两倍)就跳过这个块。最小值为 1,即任何块都不得大于 --chunk-size,但不建议真设成 1,因为 EXPLAIN 报告的行数是估算值,可能与块内实际行数不同。若因超大而跳过的块太多,可以设成比默认 2 更大的值。设为 0 可关闭超大块检查
--chunk-time类型:float;默认:0.5。动态调整块大小,使每条校验查询的执行时间为这么多秒。工具跟踪整体以及每张表各自的校验速率(行/秒),据此在每条校验查询之后调整块大小。算法是:每张表开始时,块大小按工具启动以来的总体平均行/秒初始化,若还没开始干活就用 --chunk-size 的值;此后每个块都调整块大小以逼近目标耗时,并维持一个指数衰减的每秒查询数移动平均,因此服务器负载变化导致性能变化时工具能快速适应。这让每张表以及整台服务器的查询耗时都可预期。设为 0 则块大小不再自动调整,校验查询的耗时会波动但块的行数不变;显式指定 --chunk-size 也是同样的效果
-c, --columns类型:array;组:Filter。只校验这些列(逗号分隔)。表若不含任何指定列就会被跳过。本选项作用于所有表,所以除非各表有一组共同的列,否则只在校验单张表时才有意义
--config类型:Array。读取逗号分隔的配置文件列表;如指定必须放在命令行第一个选项的位置
--[no]create-replicate-table默认:yes。--replicate 指定的库和表不存在时自动创建,结构与 --replicate 中给出的建表语句相同
-d, --databases类型:hash;组:Filter。只校验这些库(逗号分隔)
--databases-regex类型:string;组:Filter。只校验库名匹配该 Perl 正则的库。用小写名匹配;写裸正则,不要加斜杠
-F, --defaults-file类型:string。只从给定文件读取 mysql 选项,必须给绝对路径
--disable-qrt-plugin若 QRT(Query Response Time)插件已启用则禁用它
--[no]empty-replicate-table默认:yes。校验每张表之前先删掉该表以前的校验和。它不会截断整张结果表,只在开始校验某张表前删掉这张表对应的行;因此若校验提前中止且原本有旧数据,那些在中止前没被校验的表的行仍然留着。从上次校验续跑时,续跑起点那张表的校验记录也不会被清空。要清空整张结果表必须在运行工具前手工执行 TRUNCATE TABLE。另见 --truncate-replicate-table
-e, --engines类型:hash;组:Filter。只校验使用这些存储引擎的表
--explain可累加;默认:0;组:Output。只显示校验查询而不执行(同时禁用 --[no]empty-replicate-table)。指定两次时工具会真正走一遍分块算法,打印每个块的上下边界值,但仍不执行校验查询
--fail-on-stopped-replication复制已停止时直接以错误退出(退出状态 128),而不是等到复制重启
--float-precision类型:int。FLOAT 与 DOUBLE 转字符串时的精度:用 MySQL 的 ROUND() 把 FLOAT、DOUBLE 值四舍五入到小数点后指定位数。这有助于避免同一个值在不同 MySQL 版本和硬件上浮点表示不同而导致的校验和不一致。默认不做四舍五入,值由 CONCAT() 转成字符串,字符串表示由 MySQL 决定。例如指定 2,则 1.008 和 1.009 都会舍入成 1.01,校验结果相等
--function类型:string。校验和使用的散列函数(FNV1A_64、MURMUR_HASH、SHA1、MD5、CRC32 等)。默认用 CRC32()MD5()SHA1() 也可以,也可以用自己编译的 UDF。指定的函数在 SQL 里执行而不是在 Perl 里,所以 MySQL 必须能用到它。MySQL 缺少又快又好的内置散列函数:CRC32() 太容易碰撞,MD5()SHA1() 非常耗 CPU。Percona Server 附带的 FNV1A_64() UDF 是更快的替代,编译安装很简单(说明见源码头部);装了它就会优先于 MD5()。也可以编译安装 MURMUR_HASH() UDF(源码同样随 Percona Server 分发),它可能比 FNV1A_64() 更好
--help显示帮助并退出
-h, --host类型:string;默认:localhost。要连接的主机
--ignore-columns类型:Hash;组:Filter。计算校验和时忽略这些列(逗号分隔)。若一张表的所有列都被本选项过滤掉,这张表会被跳过
--ignore-databases类型:Hash;组:Filter。忽略这些库(逗号分隔)
--ignore-databases-regex类型:string;组:Filter。忽略库名匹配该 Perl 正则的库
--ignore-engines类型:Hash;默认:FEDERATED,MRG_MyISAM;组:Filter。忽略这些存储引擎
--ignore-tables类型:Hash;组:Filter。忽略这些表(逗号分隔),表名可用库名限定。--replicate 指定的结果表总是被自动忽略
--ignore-tables-regex类型:string;组:Filter。忽略表名匹配该 Perl 正则的表。用小写名匹配;写裸正则,不要加斜杠
--max-lag类型:time;默认:1s;组:Throttle。暂停校验直到所有从库的延迟都小于这个值。每条校验查询(每个块)之后,工具用 Seconds_Behind_Source 查看所有已连接从库的复制延迟,任一从库延迟超过本值就睡 --check-interval 秒再重新检查全部从库;指定了 --check-replica-lag 时只检查那一台。工具会永远等到延迟消退;任一从库被停掉时也永远等到它启动,所有从库都在运行且延迟不过大后才继续校验。等待期间打印进度报告;若某从库已停止,会立即打印一次,之后按进度间隔继续打印
--max-load类型:Array;默认:Threads_running=25;组:Throttle。每个块之后检查 SHOW GLOBAL STATUS,任一状态变量高于阈值就暂停。取逗号分隔的 MySQL 状态变量列表,每个变量后可跟 =MAX_VALUE:MAX_VALUE;不给阈值时,工具查看启动时的当前值并加 20% 作为阈值。例如只写 Threads_connected,启动时当前值为 100,则超过 120 时暂停、回落到 120 以下时继续;想显式指定 110 就写 Threads_connected:110Threads_connected=110。本选项的目的是防止工具给服务器加太多负载:校验查询若过于打扰业务或造成锁等待,服务器上其他查询会阻塞排队,通常表现为 Threads_running 上升,工具在每条校验查询结束后立刻执行 SHOW GLOBAL STATUS 就能检测到。但它只能给服务器一个从排队中恢复的机会,并不能阻止排队;发现排队时最好的办法是减小 chunk time
-s, --mysql_ssl类型:int。创建 SSL MySQL 连接
-p, --password类型:string。连接密码(含逗号需用反斜杠转义,如 exam\,ple
--pause-file类型:string。该参数指定的文件存在期间暂停执行
--pid类型:string。创建指定的 PID 文件。若 PID 文件已存在且其中的 PID 与当前 PID 不同,工具不会启动;若文件存在但其中的 PID 已不在运行,工具会用当前 PID 覆盖它。工具退出时自动删除该文件
--plugin类型:string。定义了 pt_table_checksum_plugin 类的 Perl 模块文件。插件可以挂钩到 pt-table-checksum 的许多环节,详见"插件"一节
-P, --port类型:int。连接端口
--progress类型:array;默认:time,30。向 STDERR 打印进度报告。值是两部分的逗号分隔列表:第一部分为 percentagetimeiterations,第二部分指定更新频率(百分比、秒数或迭代次数)。工具为多种耗时操作打印进度,包括等待延迟的从库追赶
-q, --quiet可累加;默认:0。只打印最重要的信息(会禁用 --progress)。指定一次只打印错误、警告和有校验差异的表;指定两次只打印错误,这时可以靠退出状态判断是否有警告或校验差异
--recurse类型:int。发现从库时在拓扑层级中递归的层数,默认无限。另见 --recursion-method 与"从库检查"
--recursion-method类型:array;默认:processlist,hosts。发现从库的首选方式。虽然运行 pt-table-checksum 并不要求存在从库,但工具发现不了的从库上的差异也就检测不到;因此若没找到从库且方式不是 none,会打印警告且退出状态非零,此时可换一种方式,或用 dsn 方式显式指定要检查的从库。可选方式:processlist(用 SHOW PROCESSLIST,默认,因为 SHOW REPLICAS 不可靠;但服务器用非标准端口即非 3306 时改以 hosts 为默认)、hosts(用 SHOW REPLICAS,MySQL 8.1 之前是 SHOW SLAVE HOSTS,要求从库配置了 report_hostreport_port 等)、cluster(用 SHOW STATUS LIKE 'wsrep_incoming_addresses' 自动发现集群节点,需要 Galera 23.7.3 及以上,如 PXC 5.5.29+;可与 processlist、hosts 组合,但属实验性)、dsn=DSN(从一张表读取从库 DSN,只连这些从库)、none(不发现从库,不推荐)。详见"发现从库与沿拓扑传播"
--replica-password类型:string。连接从库使用的密码,可与 --replica-user 一起用;该用户的密码在所有从库上必须相同
--replica-skip-tolerance类型:float;默认:1.0。当主库上一张表被标记为只用单个块校验、而从库上这张表超出可接受的最大尺寸时,该表会被跳过。由于行数往往只是粗略估算,很多表因为极小的差异被无谓跳过;本选项给出行数超出的容忍上限,例如 1.2 表示容忍从库表最多多出 20% 的行
--replica-user类型:string。连接从库使用的用户。可以让从库上用一个权限更少的不同用户,但该用户必须在所有从库上都存在
--replicate类型:string;默认:percona.checksums。把校验结果写入这张表,表结构必须与文档给出的建表语句一致(lower_boundary、upper_boundary 也可以是 BLOB,见 --binary-index)。默认 --create-replicate-table 为 yes,所以库和表不存在时会自动创建。务必为结果表选择合适的存储引擎:若校验 InnoDB 表却给结果表用 MyISAM,一次死锁就会弄坏复制——校验语句混用事务表与非事务表,出错也会被写进 binlog,在从库上重放时不死锁,于是以"主从错误不同"中断复制;这是 MySQL 复制的问题而非本工具的问题。结果表自身永远不会被校验(工具自动把它加入 --ignore-tables
--[no]replicate-check默认:yes。每校验完一张表就检查各从库有无数据差异。工具在所有检测到的从库上执行一条简单的 SELECT,把从库的校验结果与主库的校验结果做比较,差异数报告在输出的 DIFFS 列
--replicate-check-only只检查从库一致性而不执行校验查询。仅与 --[no]replicate-check 一起使用。指定后工具不校验任何表,只检查之前校验发现的差异然后退出。适合把 pt-table-checksum 静默放在 cron 里跑、之后单独要一份结果报告的场景,比如实现 Nagios 检查
--replicate-check-retries类型:int;默认:1。遇到差异时重试比对这么多次,只有差异在这么多次检查后依然存在才视为真差异。设成 2 或更大可以缓解使用 --resume 时出现的假差异
--replicate-database类型:string。只 USE 这个库。默认 pt-table-checksum 会执行 USE 切到当前处理的表所在的库,这是为了尽量避免 binlog_ignore_dbreplicate_ignore_db 这类复制过滤器带来的问题。但复制过滤器可能造成"怎么做都不对"的局面:有些语句可能复制不过去,有些则会让复制失败。这时可以用本选项指定一个固定的默认库,工具只 USE 它且不再切换。另见 --[no]check-replication-filters
--resume从最后一个已完成的块继续校验(同时禁用 --[no]empty-replicate-table)。若工具在校验完所有表之前停止,本选项让校验从它最后完成的那张表的最后一个块接着来
--retries类型:int;默认:2。发生非致命错误时对一个块重试这么多次。非致命错误指锁等待超时、查询被 kill 这类问题
--run-time类型:time。运行多久,默认一直运行到所有表都校验完。允许的时间后缀:s(秒)、m(分)、h(小时)、d(天)。与 --resume 配合可以在限定时间内校验尽可能多的表,下次运行时从上次停下的地方继续
--separator类型:string;默认:#CONCAT_WS() 使用的分隔符,校验时用它把各列的值连接起来
--set-vars类型:Array。以逗号分隔的 变量=值 列表设置 MySQL 变量。默认设置 wait_timeout=10000innodb_lock_wait_timeout=1;命令行上指定的变量会覆盖这些默认值,例如 --set-vars wait_timeout=500 会覆盖默认的 10000。无法设置某个变量时打印警告并继续
--skip-check-replica-lag类型:DSN;可重复。检查从库延迟时跳过这个 DSN,可多次使用,例如 --skip-check-replica-lag h=127.1,P=12345 --skip-check-replica-lag h=127.1,P=12346
--skip-check-slave-lag类型:DSN;可重复。已废弃,将在未来版本移除,请改用 --skip-check-replica-lag
--slave-password类型:string。已废弃,将在未来版本移除,请改用 --replica-password
--slave-user类型:string。已废弃,将在未来版本移除,请改用 --replica-user
-S, --socket类型:string。连接使用的 socket 文件
-t, --tables类型:hash;组:Filter。只校验这些表(逗号分隔),表名可用库名限定
--tables-regex类型:string;组:Filter。只校验表名匹配该 Perl 正则的表
--trim给 VARCHAR 列加上 TRIM()(便于把 4.1 与 5.0 及以上做比较)。当你不在意不同 MySQL 版本对尾部空格处理差异时有用:MySQL 5.0 及以后保留 VARCHAR 的尾部空格,更早的版本会去掉,这种差异会造成假的校验差异
--truncate-replicate-table校验开始前截断结果表。它与 --[no]empty-replicate-table 不同:后者只在开始校验某张表时删掉该表对应的行,而本选项在整个过程一开始就截断结果表,因此即使过程因错误中止,之前所有的校验信息也都丢失了
-u, --user类型:string。登录用户(若非当前用户)
--version显示版本并退出
--[no]version-check默认:yes。检查 Percona Toolkit、MySQL 等软件的最新版本与已知问题版本(详见版本检查
--where类型:string。只处理匹配该 WHERE 子句的行,可用来把校验限定在表的一部分。对只追加写入的表特别有用——不想反复重查所有行,就可以每天跑一个任务只查前一天的行。用法类似 mysqldump 的 -w 选项:不要写 WHERE 关键字,值可能需要加引号

只校验昨天以来新增的行:

bash
pt-table-checksum --where "ts > CURRENT_DATE - INTERVAL 1 DAY"

DSN 选项

DSN 部分说明
Acharset默认字符集
D-DSN 表所在的库(不复制)
Fmysql_read_default_file只从给定文件读取连接用的默认选项
hhost要连接的主机
ppassword连接密码(含逗号需用反斜杠转义)
Pport连接端口
Smysql_socket连接使用的 socket 文件
t-DSN 表的表名(不复制)
uuser登录用户(若非当前用户)
smysql_ssl创建 SSL 连接

其他信息

  • 作者:Baron Schwartz 和 Daniel Nichter

  • 致谢:Claus Jeppesen、Francois Saint-Jacques、Giuseppe Maxia、Heikki Tuuri、James Briggs、Martin Friebe、Sergey Zhuravlev

  • 通用说明:已知问题的反馈方式、PTDEBUG 调试安全提示、系统要求基线,见 通用说明

更多细节请阅读 官方文档

Percona Toolkit 中文文档 · 社区维护的第三方学习站