pt-table-checksum
在主库上执行分块校验查询,让它随复制流到从库重新执行,从而在线发现主从数据不一致。
语法
pt-table-checksum [OPTIONS] [DSN]pt-table-checksum 通过在复制源(主库)上执行校验查询来做在线一致性检查——与主库数据不一致的从库上,同一条查询会算出不同的结果。可选的 DSN 用来指定主库主机。只要发现差异,或出现任何警告与错误,工具的退出状态就非零。
连接 localhost 上的主库,校验每一张表,并在检测到的每个从库上报告结果:
pt-table-checksum本工具专注于高效地发现数据差异;差异找出来之后,用 pt-table-sync 去修复。
使用前
Percona Toolkit 久经生产验证,但所有数据库工具都可能对系统和数据库服务器造成风险。使用前请:阅读工具文档、查看已知问题、先在非生产服务器上测试、备份生产服务器并验证备份可用。另请阅读下文"限制与注意事项"。
用法示例
以下命令假定已通过选项文件或本机 socket 配置好 MySQL 连接;远程主库用 h=主库IP,P=端口,u=用户,p=密码 指定。发现差异后请用 pt-table-sync 修复。
场景:校验整个实例
连接本机主库,校验所有库表,并在发现的每个从库上报告差异:
pt-table-checksum场景:只校验某个业务库
只校验 appdb 一个库,缩短校验时间:
pt-table-checksum --databases appdb场景:只校验某张表
怀疑某张表主从不一致,只针对它校验:
pt-table-checksum --tables appdb.orders场景:只校验最近新增的数据
对只增不改的表(如流水、日志表),每天只查前一天的新增行,避免反复重查历史数据:
pt-table-checksum --databases appdb --where "ts > CURRENT_DATE - INTERVAL 1 DAY"场景:定时校验 + 事后单独取差异报告
第一步放进 cron 静默执行校验(只写结果、不比对),第二步需要时再单独比对并打印差异:
# 第一步:cron 定时执行,只写校验结果
pt-table-checksum --databases appdb --no-replicate-check# 第二步:随时取差异报告
pt-table-checksum --replicate-check-only场景:限流防拖库
控制从库延迟与服务器负载,避免校验把主从压垮:
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,库和表不存在时自动创建):
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;各列含义:
db、tbl:被校验的库名与表名。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:时间戳。
也可以手工查询结果表拿报告。下面这条查询列出所有存在差异的库和表,以及可能受影响的块数与行数汇总:
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 的整数列,可以试试:
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 UDFs、How 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 replicasprocesslist是默认方式,因为SHOW REPLICAS不可靠;但如果服务器用的是非标准端口(不是 3306),hosts会成为默认,因为这种情况下它工作得更好。hosts要求从库配置了report_host、report_port等参数。cluster需要基于 Galera 23.7.3 或更新版本的集群(如 Percona XtraDB Cluster 5.5.29 及以上),通过SHOW STATUS LIKE 'wsrep_incoming_addresses'自动发现集群节点。可以把cluster与processlist、hosts组合起来同时发现节点和从库,但这个能力是实验性的。dsn比较特殊:它不自动发现,而是指定一张存放从库 DSN 的表,工具只连接表里列出的这些从库。当从库与主库的用户名/密码不同,或者你想阻止工具连某些从库时,这种方式最合适。用法形如--recursion-method dsn=h=host,D=percona,t=dsns,给出的 DSN 必须带D和t两部分,或者带库名限定的t部分。DSN 表结构必须是:
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 排序,除此之外 id 和 parent_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-method 的 dsn 方式指定集群节点;另外,对集群节点不做延迟检查。
单个集群:最简单的 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每个钩子接收的参数不同。想知道某个钩子接收哪些参数,在工具源码里搜索钩子名,例如:
# --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=12346 和 h=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_db、replicate_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:110 或 Threads_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 打印进度报告。值是两部分的逗号分隔列表:第一部分为 percentage、time 或 iterations,第二部分指定更新频率(百分比、秒数或迭代次数)。工具为多种耗时操作打印进度,包括等待延迟的从库追赶 |
-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_host、report_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_db、replicate_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=10000 和 innodb_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 关键字,值可能需要加引号 |
只校验昨天以来新增的行:
pt-table-checksum --where "ts > CURRENT_DATE - INTERVAL 1 DAY"DSN 选项
| 键 | DSN 部分 | 说明 |
|---|---|---|
A | charset | 默认字符集 |
D | - | DSN 表所在的库(不复制) |
F | mysql_read_default_file | 只从给定文件读取连接用的默认选项 |
h | host | 要连接的主机 |
p | password | 连接密码(含逗号需用反斜杠转义) |
P | port | 连接端口 |
S | mysql_socket | 连接使用的 socket 文件 |
t | - | DSN 表的表名(不复制) |
u | user | 登录用户(若非当前用户) |
s | mysql_ssl | 创建 SSL 连接 |
其他信息
作者:Baron Schwartz 和 Daniel Nichter
致谢:Claus Jeppesen、Francois Saint-Jacques、Giuseppe Maxia、Heikki Tuuri、James Briggs、Martin Friebe、Sergey Zhuravlev
通用说明:已知问题的反馈方式、PTDEBUG 调试安全提示、系统要求基线,见 通用说明。
更多细节请阅读 官方文档。