1.前言
pt-table-checksum是Percona Toolkit工具系列中的一个,可以用来检测MySQL主、从数据的一致性。
其原理是,在主库执行校验语句(binlog格式为STATEMENT),通过复制传递到从库,如果数据不一致,则主、从会产生不同的校验值,以此来判断主从数据是否一致。
2.原理
- pt-table-checksum将每个表切分成多个块(chunks),每个块包含多行数据,每次仅对一个块进行校验。
- 它可以根据参数指定的每个块的校验执行时间,将表分成不同大小的块。
- 将单个表切分成多个块进行校验,可以保证不会引起从库太大的延迟,也不会对服务器负载造成太大干扰。默认的每个块的校验执行时间是0.5s。
- pt-table-checksum会持续跟踪服务器执行校验语句的时间,并且会随着服务器性能的变化,来动态调整分块的大小。
- 将表分块时,需要使用索引(优先使用主键或唯一键)。如果表没有索引,且表的行数量较小时,pt-table-checksum会将整个表作为一个数据块进行校验。
- pt-table-checksum会持续监控从库状态,如果从库复制延迟太大,校验会暂停,以等待从库追上主库。如果从库发生错误,或者复制停止,它也会暂停及等待。
- pt-table-checksum对于校验过程中产生的错误是有复原能力的。如果正在执行校验的SQL被kill掉,这对pt-table-checksum来说不是一个致命的错误,它会重新执行被kill掉的查询语句;如果再次失败,则会跳过,继续进行表的下一个分块的校验。
当出现锁等待超时的时候,也会进行相同的重试行为,同时输出警告信息。
如果连接服务器失败,pt-table-checksum也会进行重连和继续进行校验。 - 当在运行的过程中被完全终止后,可以使用
--resume
参数,继续从上次终止时正在处理的表的当前分块开始,继续校验。
我们也可以使用CTRL-C
按键来停止校验,pt-table-checksum会在完成对当前正在处理的分块的校验后,正常退出。之后我们也可以启动程序继续进行校验。 - 当pt-table-checksum完成对一个表的所有分块的校验后,它会暂停并等待所有检测到的从库执行完这个表的校验语句。一旦从库执行完成,它会比对主、从的校验结果是否一致,并输出一行结果
3.使用限制
- pt-table-checksum要求复制是基于STATEMENT格式的,并且它在运行时会在主库上设置
binlog_format=STATEMENT
,但是由于MySQL的限制,这一设置并不会传递到从库。
因此当从库的binlog格式是ROW格式时,无法直接对从库的从库进行校验。
pt-table-checksum会自动检测所有服务器的binlog_format
,可以通过参数--[no]check-binlog-format
来指定检测与否。 - pt-table-checksum假设主从的数据库和表结构都是一致的。
如果主库上的库不存在于从库上,或者从库的表结构与主库不一致,将会导致主从复制中断。
4.参数详见
https://www.percona.com/doc/percona-toolkit/3.0/pt-table-checksum.html#options
5.命令使用(以实践过)
pt-table-checksum -upt_table_check -p123 -S mysql.sock --tables=liulin.t1 --no-check-binlog-format --replicate=percona_schema.checksums --recursion-method=hosts
注意1:-u :用户命令 -p 用户密码 -->这里最好在主库创建创建一个用户,然后通过主从复制同步到各个从库中
注意2: --replicate 参数表示在主库和从库下的percona_schema库下创建一张checksums表,该表很重要,一般是在我们执行完命令后,通过主从库的这张表进行验证主从库数据是否一致
注意3: --recursion-method=hosts :这个参数表示主库通过hosts连接和认证从库,注意当开启这个参数时,必须要在从库中配置:report_host参数和report_port参数,填写当前数据库的ip地址和端口号
6.输出
pt-table-checksum的输出有两种格式:带--replicate-check-only
参数和正常不带这个参数时,输出结果的格式是不同的。
不带--replicate-check-only 参数输出
pt-table-checksum在完成校验后会以表格形式输出结果,每个表的校验结果为一行输出,每完成校验一张表会输出一行校验结果。
输出格式如下:
TS ERRORS DIFFS ROWS CHUNKS SKIPPED TIME TABLE 09-12T10:34:38 0 0 1 1 0 0.059 road_to_dba.names 09-12T10:34:38 0 0 5 1 0 0.016 road_to_dba.t_bigint
各列的含义如下:
- TS
pt-table-checksum完成该表的校验时的时间。 - ERRORS
在校验表的过程中出现的错误和警告的数量。
错误和警告信息在校验的过程中会以标准错误的形式输出。 - DIFFS
主库数据与一个(或多个)从库数据不一致的分块的数量。
当指定--no-replicate-check
时,该值始终为0。当指定--replicate-check-only
时,只输出数据不一致的表的校验结果。 - ROWS
查询和校验的表的行数。 - CHUNKS
表被分成的分块的数量。 - SKIPPED
校验表时,跳过的分块的数量。
数据分块被跳过可能由以下问题引起:- MySQL没有使用
--chunk-index
指定的索引; - 通过分析执行计划(
--[no]check-plan
)检测到MySQL没有使用全部的表分块使用的索引; - 数据分块的大小大于
--chunk-size * --chunk-size-limit
; - 锁等待超时(重试的次数大于
--retries
指定的值); - 校验的查询语句被kill掉(重试的次数大于
--retries
指定的值)
- MySQL没有使用
- TIME
表的校验执行时长。 - TABLE
校验的表名(含库名)。
带--replicate-check-only 输出
如果指定了--replicate-check-only
参数,则只会输出校验结果不一致的从库数据。
输出结果的格式为:每一个从库为一个段落,每一个校验结果不同的分块占一行,各列值之间以空格分隔。如下
Differences on node601 TABLE CHUNK CNT_DIFF CRC_DIFF CHUNK_INDEX LOWER_BOUNDARY UPPER_BOUNDARY road_to_dba.tbl1 1 0 1 PRIMARY 1 100 road_to_dba.tbl1 6 0 1 PRIMARY 501 600 Differences on node603 TABLE CHUNK CNT_DIFF CRC_DIFF CHUNK_INDEX LOWER_BOUNDARY UPPER_BOUNDARY road_to_dba.tbl1 1 0 1 PRIMARY 1 100 road_to_dba.tbl2 9 5 0 PRIMARY 101 200
输出的每个段落的第一行表示校验结果与主库不一致的从库的信息。
各列的含义如下:
- TABLE
与主库数据不一致的表的名称(包含库名)。 - CHUNK
表中与主库数据不一致的分块号。 - CNT_DIFF
从库的分块的行数量
减去主库的分块的行数量
的差值。 - CRC_DIFF
如果从库分块的CRC校验值与主库的不同,则为1;否则,为0。 - CHUNK_INDEX
将表进行分块所使用的索引。 - LOWER_BOUNDARY
分块的下边界所使用的索引值。 - UPPER_BOUNDARY
分块的上边界所使用的索引值。
7.举例:
在主库上执行
校验表road_to_dba.t_int主从数据的一致性:
shell> ./bin/pt-table-checksum --socket=/tmp/mysql3376.sock --user=pt_table_check --password='checksum123' --tables=road_to_dba.t_int --no-check-binlog-format --replicate=percona_schema.checksums
结果:
TS ERRORS DIFFS ROWS CHUNKS SKIPPED TIME TABLE 09-19T18:50:19 0 1 9 1 0 0.042 road_to_dba.t_int
可以从DIFFS列看出有一个数据分块的主从数据不一致
查看差异汇总
在从库执行
mysql> SELECT * FROM percona_schema.checksums WHERE master_cnt <> this_cnt OR master_crc <> this_crc OR ISNULL(master_crc) <> ISNULL(this_crc); +-------------+-------+-------+------------+-------------+----------------+----------------+----------+----------+------------+------------+---------------------+ | db | tbl | chunk | chunk_time | chunk_index | lower_boundary | upper_boundary | this_crc | this_cnt | master_crc | master_cnt | ts | +-------------+-------+-------+------------+-------------+----------------+----------------+----------+----------+------------+------------+---------------------+ | road_to_dba | t_int | 1 | 0.000546 | NULL | NULL | NULL | 2e1ed7d3 | 10 | 17818634 | 9 | 2016-09-19 18:50:19 | +-------------+-------+-------+------------+-------------+----------------+----------------+----------+----------+------------+------------+---------------------+ 1 row in set (0.00 sec)
从以上查询可以看出:
表road_to_dba.t_int的
分块1
的主从数据不一致。该分块在主库的行数(master_cnt
)为9
,在从库的行数(this_cnt
)为10
;该分块在主库的校验值(master_crc
)为17818634
,在从库的校验值(this_crc
)为2e1ed7d3
;