pt-table-checksum用来检测主从数据库上的数据一致性,其原理是通过在主库上运行一系列的MySQL函数计算每个表的散列值,并利用主从关系将相同的操作在从服务器上重放(基于statement格式的binlog),从而获取到主从服务器上的散列值然后比较散列值判定主从数据是否一致。
对于表中的单行数据,先检查每一列的数据类型,然后将所有的数据类型转换为字符串,再使用concat_ws()函数进行连接,然后根据连接后得到的字符串计算出checksum的值,默认的checksum算法为crc32。
对于数据量较大的表,如果单次计算整表的checksum值,会导致主库性能压力和主从延迟,因此会根据chunk_size和chunk_time等参数来对表进行拆分为多个chunk,计算chunk时,将该chunk中所有行的数据拼接起来进行checksum计算。
=================================================
操作原理
在开始checksum计算时,会先修改binlog_format为STATEMENT,使得主库上进行的计算操作在从库上重放。
/*!50108 SET @@binlog_format := 'STATEMENT'*/
然后对表数据拆分为chunk进行checksum计算,由于使用STATEMENT的二进制格式,主库和从库都会进行分别计算,如果主库数据不一致,则会导致this_cnt和this_crc两列的数据不同。
如下面对`testdb1`.`tb1001`表的`id` >= '4621' and `id` <= '158655'作为一个chunk进行checksum计算并插入到目标表的语句
REPLACE INTO `pt`.`checksums` (db, tbl, chunk, chunk_index, lower_boundary, upper_boundary, this_cnt, this_crc)
SELECT 'testdb1', 'tb1001', '3', 'PRIMARY', '4621', '158655',
COUNT(*) AS cnt,
COALESCE(LOWER(CONV(BIT_XOR(CAST(CRC32(CONCAT_WS('#', `id`,`c1`, `c6`, `c3`,
CONCAT(ISNULL(`c1`), ISNULL(`c6`), ISNULL(`c3`)))) AS UNSIGNED)), 10, 16)),) AS crc
FROM `testdb1`.`tb1001`
FORCE INDEX(`PRIMARY`)
WHERE ((`id` >= '4621'))
AND ((`id` <= '158655')) /*checksum chunk*/
然后获取主库的上该chunk的计算值this_cnt和this_crc,将这两值更新到master_crc和master_cnt列中
SELECT this_crc, this_cnt FROM `pt`.`checksums` WHERE db = 'testdb1' AND tbl = 'tb1001' AND chunk = '3';
UPDATE `pt`.`checksums` SET chunk_time = '0.266288', master_crc = '8580b9c2', master_cnt = '90552' WHERE db = 'testdb1' AND tbl = 'tb1001' AND chunk = '3'
上面更新操作传递到从库上后,从库上的`pt`.`checksums` 表便拥有主库的checksum值和从库自己的checksum值,因此在从库上将master_crc和master_cnt列与this_cnt和this_crc两列进行对比即可以判断该chunk段数据是否有差异。
=================================================
使用要求
1、对执行检查的账户,需要有SELECT, PROCESS, SUPER, REPLICATION SLAVE的权限
GRANT ALL ON *.* TO TO 'checksum_user'@'%' IDENTIFIED BY 'checksums_pwd';
2、主从复制正常
=================================================
常用参数:
--host=<MASTER_HOST> ##主库IP
--port=<MASTER_PORT> ##主库端口
--user=<MASTER_USER> ##主库用户
--password=<MASTER_PASSWORD> ##用户密码
--replicate=test.checksums ##checksum结果存放的表
--databases=<check_database> ##要检查的数据库名
--nocheck-replication-filters ##忽略replication-do-db规则
--check-replication-filters ##如果设置replication-do-db规则,则不进行checksum
--ignore-databases=<ignore_database_name> ##不检查的数据库
--tables=<table_name> ##需要检查的表
--ignore-tables=<ignore_table_name> ##不检查的表
--recursion-method=<methon_name> ##processlist,hosts,默认使用SHOW PROCESSLIST来获取从库信息
=================================================
常用脚本:
## 检查除test和mysql以外所有数据库,并将检查结果放入到percona_db.checksum_result
pt-table-checksum \
--host="127.0.0.1" \
--port=3358 \
--user="checksum_user" \
--password="checksums_pwd" \
--recursion-method="processlist" \
--nocheck-binlog-format \
--nocheck-plan \
--nocheck-replication-filters \
--replicate=percona_db.checksum_result\
--set-vars="innodb_lock_wait_timeout=120" \
--ignore-databases="test,mysql"
## 查看表级别是否存在数据不一致
## pt-table-checksum命令执行成功后,在从库上查询 SELECT db AS database_name,
tbl AS table_name,
SUM(this_cnt) AS total_rows, COUNT(*) AS chunks
FROM `percona_db`.`checksum_result`
WHERE ( master_cnt <> this_cnt
OR master_crc <> this_crc
OR ISNULL(master_crc) <> ISNULL(this_crc) )
GROUP BY db, tbl;
## 查看chunk级别是否存在数据不一致
## pt-table-checksum命令执行成功后,在从库上查询
SELECT *
FROM `percona_db`.`checksum_result`
WHERE ( master_cnt <> this_cnt
OR master_crc <> this_crc
OR ISNULL(master_crc) <> ISNULL(this_crc) );
=================================================
使用DNS方式
recursion-method有四种参数:
1、processlist,在主库上使用SHOW PROCESSLIST方式查询从库信息
2、hosts,在主库上使用SHOW SLAVE HOSTS方式查询从库信息
3、dsn,使用DSN方式获取从库信息
4、no,不考虑
DSN(Data Source Name),数据源名称,DSN包含数据库连接信息,使用逗号将多个连接参数分割。
D: DSN表所在的数据库名。
h: 从库的host。
p: 从库的密码。当密码包括逗号(,)时,需要使用反斜杠转义。
P: 从库的端口。
S: 连接使用的socket文件。
t: 存储DSN信息的DSN表名。
u: 从库的MySQL用户名。
假设当前有服务器信息:
主库:192.168.1.101:3358
从库:192.168.1.102:3359
Canel:192.168.1.102:3360
由于当前有从库和Canel都从主库请求日志,且端口不同,如果只希望检查主从之间的数据一致,则可以使用DSN方式。
首先在主库上创建表并录入要检查从库的信息:
CREATE DATABASE percona_db;
CREATE TABLE percona_db.slave_dsn (
`id` int(11) NOT NULL AUTO_INCREMENT,
`parent_id` int(11) DEFAULT NULL,
`dsn` varchar(255) NOT NULL,
PRIMARY KEY (`id`)
);
INSERT INTO percona_db.slave_dsn(dsn)
VALUES('h=192.168.1.102,P=3359,u=checksum_user,p=checksums_pwd');
使用DSN方式进行检查
pt-table-checksum \
--host="192.168.1.101" \
--port=3358 \
--user="checksum_user" \
--password="checksums_pwd" \
--recursion-method dsn=D=percona_db,t=slave_dsn \
--nocheck-binlog-format \
--nocheck-plan \
--nocheck-replication-filters \
--replicate=percona_db.checksum_result \
--set-vars="innodb_lock_wait_timeout=120" \
--ignore-databases="test,mysql"