使用pt-table-checksum及pt-table-sync校验复制一致性
一、简介
pt-table-checksum是percona-toolkit系列工具中的一个, 可以用来检测主、 从数据库中数据的一致性。其原理是在主库上运行, 对同步的表进行checksum, 记录下来。 然后对比主从中各个表的checksum是否一致, 从而判断数据是否一致。检测过程中以块为单位, 对于大的表可以区分为多个块, 从而避免锁表( 根据唯一索引将表切分为块)检测时会自动判断复制延迟、 master的负载, 超过阀值后会自动将检测暂停。
pt-table-sync,顾名思义,它用来修复多个实例之间数据的不一致。它可以让主从的数据修复到最终一致,也可以使通过应用双写或多写的多个不相关的数据库实例修复到一致。同时它还内部集成了pt-table-checksum的校验功能,可以一边校验一边修复,也可以基于pt-table-checksum的计算结果来进行修复。
二、工作原理
1. 单行数据checksum值的计算
计算逻辑与pt-table-checksum一样,也是先检查表结构,并获取每一列的数据类型,把所有数据类型都转化为字符串,然后用concat_ws()函数进行连接,由此计算出该行的checksum值。checksum默认采用crc32计算。
2. 数据块checksum值的计算
同pt-table-checksum工具一样,pt-table-sync会智能分析表上的索引,然后把表的数据split成若干个chunk,计算的时候以chunk为单位。可以理解为把chunk内所有行的数据拼接起来,再计算crc32的值,即得到该chunk的checksum值。
3. 坏块检测和修复
前面两步,pt-table-sync与pt-table-checksum的算法和原理一样。再往下,就开始有所不同:
pt-table-checksum只是校验,所以它把checksum结果存储到统计表,然后把执行过的sql语句记录到binlog中,任务就算完成。语句级的复制把计算逻辑传递到从库,并在从库执行相同的计算。pt-table-checksum的算法本身并不在意从库的延迟,延迟多少都一样计算(有同事对此不理解,可以参考我的前一篇文章),不会影响计算结果的正确性(但是我们还是会检测延迟,因为延迟太多会影响业务,所以总是要加上—max-lag来限流)。
pt-table-sync则不同。它首先要完成chunk的checksum值的计算,一旦发现主从上同样的chunk的checksum值不同,就深入到该chunk内部,逐行比较并修复有问题的行。其计算逻辑描述如下(以修复主从结构的数据不一致为例,业务双写的情况修复起来更复杂—因为涉及到冲突解决和基准选择的问题,限于篇幅,这里不介绍):
对每一个从库,每一个表,循环进行如下校验和修复过程。对每一个chunk,在校验时加上for update锁。一旦获得锁,就记录下当前主库的show master status值。
在从库上执行select master_pos_wait()函数,等待从库sql线程执行到show master status得到的位置。以此保证,主从上关于这个chunk的内容均不再改变。
对这个chunk执行checksum,然后与主库的checksum进行比较。
如果checksum相同,说明主从数据一致,就继续下一个chunk。
如果checksum不同,说明该chunk有不一致。深入chunk内部,逐行计算checksum并比较(单行的checksum的比较过程与chunk的比较过程一样,单行实际是chunk的size为1的特例)。
如果发现某行不一致,则标记下来。继续检测剩余行,直到这个chunk结束。
对找到的主从不一致的行,采用replace into语句,在主库执行一遍以生成该行全量的binlog,并同步到从库,这会以主库数据为基准来修复从库;对于主库有的行而从库没有的行,采用replace在主库上插入(必须不能是insert);对于从库有而主库没有的行,通过在主库执行delete来删除(pt-table-sync强烈建议所有的数据修复都只在主库进行,而不建议直接修改从库数据;但是也有特例,后面会讲到)。
直到修复该chunk所有不一致的行。继续检查和修复下一个chunk。
直到这个从库上所有的表修复结束。开始修复下一个从库。
三、校验测试
1.相关简介:
Mysql Version : 5.1.72
OS Version : CentOS release 6.4 (Final)
Master IP : 10.1.1.6
Slave IP : 10.1.1.7
2.Master服务器安装Yum依赖包:
yum install perl perl-devel perl-Time-HiRes perl-DBI perl-DBD-MySQL
3.安装percona-toolkit工具包
wget http://www.percona.com/get/percona-toolkit.tar.gz
tar zxf percona-toolkit-2.2.13.tar.gz
cd percona-toolkit-2.2.13
perl Makefile.PL
make && make install
4.Master / Slave 数据库创建用户及授权。如果用户权限够大的话,不用提前创建percona数据库和checksums表,会自动创建的。
CREATE DATABASE IF NOT EXISTS percona ;
CREATE TABLE IF NOT EXISTS percona.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,
master_crc char(40) NULL,
master_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;
GRANT UPDATE,INSERT,DELETE,SELECT, PROCESS, SUPER, REPLICATION SLAVE ON *.* TO 'checksums'@'%' identified by 'Ll2g$26Dqm';
GRANT ALL ON percona.* TO ‘checksums‘@'%' IDENTIFIED BY 'Ll2g$26Dqm';
5.校验(Master服务器运行)
pt-table-checksum --nocheck-binlog-format --nocheck-plan --nocheck-replication-filters --set-vars innodb_lock_wait_timeout=120 --recursion-method=processlist --databases=data_etl -u'root' -p'nihao' -h10.0.20.38 (推荐)
或
pt-table-checksum --nocheck-binlog-format --nocheck-plan --nocheck-replication-filters --replicate=pt.checksums --set-vars innodb_lock_wait_timeout=120 --recursion-method=processlist --databases=data_etl -u'root' --ask-pass -h10.0.20.38
这里纠正一个认识,网上很多人说 pt-table-checksum 要在主库上执行,其实不是的,我的mysql实例比较多,只需在某一台服务器上安装percona-toolkit,这台服务能够同时访问主库和从库就行了,只需修改 -h 指定的ip即可。
常用参数:
--nocheck-replication-filters :不检查复制过滤器,检测中忽略mysql配置参数binlog_ignore_db等,建议启用。后面可以用--databases来指定需要检查的数据库。 --no-check-binlog-format : 不检查复制的binlog模式,因为该工具只检查主从日志格式binlog模式必须是Statement,如果是ROW则会报错,此时则需要添加该参数。 --replicate-check-only :只显示不同步的信息。 --replicate= :把checksum的信息写入到指定的库中的表中,如果不使用该选项则会默认自动创建 percona.checksums 表,默认存储到percona库的checksums表中。 --databases= :指定需要被检查的数据库,多个则用逗号隔开 --tables= :指定需要被检查的表, 多个则用逗号隔开 --host=127.0.0.1 或-h127.0.0.1 或 h=127.0.0.1 :Master的地址 --user=root 或-uroot 或 u=root :用户名 --password=123456 或-p123456 或 p=123456 :密码,如有特殊字符时需要使用引号 --port=3306 或-P3306 或 P=3306 :端口
--socket= :指定socket接口路径
值得注意的是采用最右边格式时,中间需要加逗号隔开: h=127.0.0.1,u=root,p='123456',P=3306 ,左边两种则不用。
--recursion-method : 在有些情况下,recursion-method如果不设会报错:Diffs cannot be detected because no slaves were found.
其参数有四:processlist/hosts/dsn=DSN/no,用来决定查找slave的方式是show full processlist还是show slave hosts还是命令行直接指定还是压根就不准备找从库
默认采用show full processlist来查找从库,但是这只有在主从实例端口相同的情况下才有效
--ask-pass 不在命令行指定密码,手动输入
--chunk-size, --chunk-size-limit 用于指定检测块的大小。 可控性更强
--ignore-databases/tables/column 跳出指定元素的过滤
--lock-wait-timeout innodb 锁的超时设定, 默认为1
--max-load 设置最大并发连接数
--replicate-check-only 只输出数据不一致的信息。
--help 有这个就行了, 以及其他的详见文档。
更多的参数请见官网,上面指出来的是常用的,对该场景够用的参数。
备注:--no-check-binlog-format 忽略检查binlog格式,否则会报错,默认会去检查statement模式,一般我们都用row模式
输出结果如下:
TS ERRORS DIFFS ROWS CHUNKS SKIPPED TIME TABLE 05-08T16:21:06 0 1 4 1 0 0.012 rep_test.test1
TS :完成检查的时间。 ERRORS :检查时候发生错误和警告的数量。 DIFFS :0表示一致,1表示不一致。当指定--no-replicate-check时,会一直为0,当指定--replicate-check-only会显示不同的信息。 ROWS :表的行数。 CHUNKS :被划分到表中的块的数目。 SKIPPED :由于错误或警告或过大,则跳过块的数目。 TIME :执行的时间。 TABLE :被检查的表名。
备注:主要观察DIFFS列,标识差异数。
pt-table-checksum运行在主库上,通过show processlist或show slave hosts或DSN方式来确定从库并连接,默认使用crc32算法来进行数据校验,该工具之所以需要把binlog设置为statement格式,是因为该工具能得出主从是否一致所依赖的就是statement基础上同样的SQL语句在主从库上各自的执行结果,主库进行检查后sql语句传给从库,从库执行一遍后,也得到自己的结果。
6.查看差异(Slave库运行)
执行pt-table-checksum校验结束后,在每个从库上,执行如下的sql语句即可看到是否有主从不一致发生:
mysql> select db, tbl, sum(this_cnt) as total_rows, count(*)as chunks from checksums where ( master_cnt <> this_cnt OR master_crc <> this_crc OR isnull(master_crc) <> isnull(this_crc) ) group by db, tbl;
+------+-----------+------------+--------+
| db | tbl | total_rows | chunks |
+------+-----------+------------+--------+
| apps | msg_log_6 | 1 | 1 |
+------+-----------+------------+--------+
或
mysql> select * from percona.checksums where master_cnt <> this_cnt OR master_crc <> this_crc OR ISNULL(master_crc) <> ISNULL(this_crc) \G
*************************** 1. row ***************************
db: apps
tbl: msg_log_6
chunk: 1
chunk_time: 0.15145
chunk_index: NULL
lower_boundary: NULL
upper_boundary: NULL
this_crc: e663eb4c
this_cnt: 1
master_crc: e663eb44
master_cnt: 1
ts: 2016-05-05 16:46:34
1 row in set (0.00 sec)
如果数据一致没问题的话,会无任何输出,只会显示不一致的某个表的相关信息。
7.复制,消除差异 ---- (主或从上执行都可)
假设newtables数据库tb1表存在差异
- 自动消除差异(不推荐)
pt-table-sync --print --execute --sync-to-master h=10.0.0.7,P=3306,u=checksums,p='checksums' --databases=newtables --tables=tb1
- 打印出消除差异需要执行的sql语句,人工干预到Slave库执行(推荐) -----------此处的host 10.0.0.7 为从节点
pt-table-sync --print --sync-to-master h=10.0.0.7,P=3306,u=checksums,p='checksums' --databases=newtables --tables=tb1
或者
pt-table-sync --print --sync-to-master h=10.0.0.7,P=3306,u=checksums,p='checksums' --replicate percona.checksums
--sync-to-master :指定一个DSN,即从的IP,他会通过show processlist或show slave status 去自动的找主。
--replicate :指定通过pt-table-checksum得到的表,这2个工具差不多都会一直用。
--print :打印,但不执行命令。
--execute :执行命令。
--databases :指定执行同步的数据库,多个用逗号隔开。
--tables:指定执行同步的表,多个用逗号隔开。
备注:Slave需要授权主库Drop 和Create Temporary Tables权限
8.检验
重新执行一次pt-table-checksum,查看是否还存在差异。
四、注意事项
1.采用replace into来修复主从不一致,必须保证被replace的表上有主键或唯一键,否则replace into退化成insert into,起不到修复的效果。这种情况下pt-table-sync会采用其他校验和修复算法,但是效率非常低,例如对所有列的group by然后求count(*)(表一定要有主键!)。
2.主从数据不一致需要通过replace into来修复,该sql语句必须是语句级。pt-table-sync会把它发起的所有sql语句都设置为statement格式,而不管全局的binlog_format值。这在级联A-B-C结构中,也会遇到pt-table-checksum曾经遇到的问题,引起行格式的中继库的从库卡库是必然。不过pt-table-sync默认会无限递归的对从库的binlog格式进行检查并警告。
3.由于pt-table-sync每次只能修复一个表,所以如果修复的是父表,则可能导致子表数据连带被修复,这可能会修复一个不一致而引入另一个不一致;如果表上有触发器,也可能遇到同样问题。所以在有触发器和主外键约束的情况下要慎用。pt-table-sync工具同样也不欢迎主从异构的结构。pt-table-sync工具默认会进行先决条件的检查。
4.pt-table-sync在修复过程中不能容忍从库延迟,这正好与pt-table-checksum相反。如果从库延迟太多,pt-table-sync会长期持有对chunk的for update锁,然后等待从库的master_pos_wait执行完毕或超时。从库延迟越大,等待过程就越长,主库加锁的时间就越长,对线上影响就越大。因此要严格设置max-lag。
5.对从库数据的修复通常是在主库执行sql来同步到从库。因此,在有多个从库时,修复某个从库的数据实际会把修复语句同步到所有从库。数据修复的代价取决于从库与主库不一致的程度,如果某从库数据与主库非常不一致,举例说,这个从库只有表结构,那么需要把主库的所有数据重新灌一遍,然后通过binlog同步,同时会传递到所有从库。这会给线上带来很大压力,甚至拖垮集群。正确的做法是,先用pt-table-checksum校验一遍,确定不一致的程度:如果不同步的很少,用pt-table-sync直接修复;否则,用备份先替换它,然后用pt-table-sync修复。 说明: 这实际提供了一种对myisam备份的思路:如果仅有一个myisam的主库,要为其增加从库,则可以:先mysqldump出表结构到从库上,然后启动同步,然后用pt-table-sync来修复数据。
注意:要是表中没有唯一索引或则主键则会报错:
Can't make changes on the master because no unique index exists at /usr/local/bin/pt-table-sync line 10591.
参考资料:
https://segmentfault.com/a/1190000004309169
http://www.cnblogs.com/xiaoyanger/p/5584554.html