Mysql 在生产环境中总会遇到主从数据不一致引起的同步中断,或同步中断忽略跳过错误,就会出现数据不一致,通常要修复数据不一致,最有效方法就是主库锁库备份从库恢复数据,但实际生产环境中由于数据量大,备份恢复时间长,在恢复过程严重影响业务正常使用,所以这种方式很少在实际业务中用于修复数据,以下提供可以用于检测修复主从数据一致性工具
percona提供了一个很好的工具:percona-toolkit中的 pt-table-checksum 和 pt-table-sync
pt-table-checksum是著名的 percona-toolkit 工具集的工具之一。它通过在主库执行基于statement的sql语句来生成主库数据块的checksum,把相同的sql语句传递到从库,并在从库上计算相同数据块的checksum,最后,比较主从库上相同数据块的checksum值,由此判断主从数据是否一致。这种校验是分表进行的,在每个表内部又是分块进行的,而且pt工具本身提供了非常多的限流选项,因此对线上服务的冲击较小。
checksum计算原理
1. 单行数据checksum值的计算
pt工具先检查表的结构,并获取每一列的数据类型,把所有数据类型都转化为字符串,然后用 concat_ws() 函数进行连接,由此计算出该行的checksum值。checksum默认采用crc32,你可以自己定义效率更高的udf。
2. 数据块checksum值的计算
如果一行一行的计算checksum再去和从库比较,那么效率会非常低下。pt工具选择智能分析表上的索引,然后把表的数据split成一个个chunk,计算的时候也是以chunk为单位。因此引入了聚合函数 BIT_XOR() 。它的功能可以理解为把这个chunk内的所有行的数据拼接起来,再计算crc32的值,就得到这个chunk的checksum值。这其中还有count(*),用来计算chunk包含的行数。每一次对chunk进行checksum后,pt工具都会对耗时进行统计分析,并智能调整下一个chunk的大小,避免chunk太大对线上造成影响,同时也要避免chunk太小而效率低下。
3. 一致性如何保证
当pt工具在计算主库上某chunk的checksum时,主库可能还在更新,同时从库可能延迟使得relay-log中还有与这个chunk数据相关的更新,加for update当前读锁,这保证了主库的某个chunk内部数据的一致性。否则,1000个人chekcusm同样的1000行数据,可能得到1000个不同的结果,你无法避开mvcc的干扰!获得for update锁后,pt工具开始计算chunk的checksum值,并把计算结果保存到pt工具自建的结果表中(采用replace into select的方式),然后释放锁。该语句最终会传递到从库并执行相同的计算逻辑。
环境如下:
centos 6.9 64bit
mysql 5.6.35 x64
安装percona-toolkit工具
yum install https://www.percona.com/redir/downloads/percona-release/redhat/0.1-6/percona-release-0.1-6.noarch.rpm
yum install percona-toolkit
常用选项:
–check-replication-filters 是否检查复制过滤规则
–check-slave-tables 检查是否所有从库都有被检查的表和列
–chunk-size-limit 每个chunk最大不能超过这个大小,超过就忽略它
–check-interval 多久检查一次主从延迟、主库负载是否达到上限
–check-slave-lag 是否只检查这个从库的延迟
–max-lag 最大延迟,超过这个就等待
–max-load 最大负载,超过这个就等待
–databases 只检查某些库
–tables 只检查某些表
–resume 因某种原因中断,下次接着执行,不用从头开始
–chunk-time 每个chunk被计算的时间,一般默认为0.5秒
–recursion-method:发现从库的方式。pt-table-checksum 默认可以在主库的 processlist 中找到从库复制进程,从而识别出有哪些从库,但如果使用是非标准3306端口,会导致找不到从库信息。此时就会自动采用host方式,但需要提前在从库 my.cnf 里面配置report_host、report_port信息,如:
report_host = MASTER_HOST
report_port = 13306
分别在主从master/slave服务器授权用户的权限
GRANT SELECT, INSERT, UPDATE, DELETE, PROCESS, SUPER, REPLICATION SLAVE ON *.* TO 'percona_tk'@'192.168.%' IDENTIFIED BY '123456';
在master服务器上执行
pt-table-checksum --no-check-binlog-format --replicate=test1.checksums --databases=test1 h=MASTER_HOST,u=percona_tk,p='123456',P=3306 --recursion-method=processlist
[root@zk-kafka-n4 soft]# pt-table-checksum --no-check-binlog-format --replicate=test1.checksums --databases=test1 h=127.0.0.1,u=percona_tk,p=123456,P=3306 --recursion-method=processlist
Checking if all tables can be checksummed ...
Starting checksum ...
TS ERRORS DIFFS ROWS DIFF_ROWS CHUNKS SKIPPED TIME TABLE
06-21T10:57:42 0 0 4 0 1 0 0.259 test1.test
- TS :完成检查的时间戳。
- ERRORS :检查时候发生错误和警告的数量。
- DIFFS :不一致的chunk数量。当指定 --no-replicate-check 即检查完但不立即输出结果时,会一直为0;当指定 --replicate-check-only 即不检查只从checksums表中计算crc32,且只显示不一致的信息(毕竟输出的大部分应该是一致的,容易造成干扰)。
- ROWS :比对的表行数。
- CHUNKS :被划分到表中的块的数目。
- SKIPPED :由于错误或警告或过大,则跳过块的数目。
- TIME :执行的时间。
- TABLE :被检查的表名
用dsns方式检测连接从,适合于主从非默认端口,processlist方式连接不上,host方式修改配置文件不想重启服务等。
REPLICA_HOST:3306 从库
PTCHECK_HOST pt-table-checksum所在服务器
DSN_DBHOST,记录从库(连接)dsns的数据库
dsn指定从库了,在同网段数据库主机里装上 percona-toolkit,不一定要在主从的服务器上使用
在DSN_DBHOST 数据库实例上创建DSNs表:
create database percona;
CREATE TABLE `percona`.`dsns` (
`id` int(11) NOT NULL AUTO_INCREMENT,
`parent_id` int(11) DEFAULT NULL,
`dsn` varchar(255) NOT NULL,
PRIMARY KEY (`id`)
);
GRANT ALL PRIVILEGEES on percona.* to percona_tk@'PTCHECK_HOST' IDENTIFIED BY 'percona_pass';
有多个实例要检查,可以创建多个类似的dsns表。上面的percona_tk用户只是用来访问dsn库。插入从库信息:
use percona;
insert into dsns(dsn) values('h=REPLICA_HOST,P=3306,u=repl_user,p=repl_pass');
DSNs记录dsn列格式如 h=REPLICA_HOST,u=repl_user,p=repl_pass
在 PTCHECK_HOST 上执行检查命令:
pt-table-checksum --no-check-binlog-format --replicate=test1.checksums --databases=test1 h=MASTER_HOST,u=percona_tk,p='123456',P=3306 --recursion-method dsn=h=DSN_DBHOST,u=percona_tk,p='percona_pass',P=3306,D=percona,t=dsns
percona提供了pt-table-sync 用于修复主从数据的一致性
pt-table-sync [OPTIONS] DSN [DSN]。
pt-table-sync: 高效的同步MySQL表之间的数据,他可以做单向和双向同步的表数据。他可以同步单个表,也可以同步整个库。它不同步表结构、索引、或任何其他模式对象。所以在修复一致性之前需要保证他们表存在。
打印修复的SQL语句(只打印不修复)
[root@zk-kafka-n4 soft]# pt-table-sync --print --replicate=test1.checksums --databases=test1 h=192.168.3.68,u=percona_tk,p=123456,P=3306 h=192.168.3.67,u=percona_tk,p=123456,P=3306
DELETE FROM `test1`.`test` WHERE `sdf`='888' LIMIT 1 /*percona-toolkit src_db:test1 src_tbl:test src_dsn:P=3306,h=192.168.3.68,p=...,u=percona_tk dst_db:test1 dst_tbl:test dst_dsn:P=3306,h=zk-kafka-n3,p=...,u=percona_tk lock:1 transaction:1 changing_src:test1.checksums replicate:test1.checksums bidirectional:0 pid:16264 user:root host:zk-kafka-n4*/;
DELETE FROM `test1`.`test` WHERE `sdf`='990' LIMIT 1 /*percona-toolkit src_db:test1 src_tbl:test src_dsn:P=3306,h=192.168.3.68,p=...,u=percona_tk dst_db:test1 dst_tbl:test dst_dsn:P=3306,h=zk-kafka-n3,p=...,u=percona_tk lock:1 transaction:1 changing_src:test1.checksums replicate:test1.checksums bidirectional:0 pid:16264 user:root host:zk-kafka-n4*/;
先MASTER的IP,再SLAVE的IP
以上是打印出同步语句
参数选项:
- --replicate= :指定通过pt-table-checksum得到的表,这2个工具差不多都会一直用。
- --databases= : 指定执行同步的数据库,多个用逗号隔开。
- --tables= :指定执行同步的表,多个用逗号隔开。
- --sync-to-master :指定一个DSN,即从的IP,他会通过show processlist或show slave status 去自动的找主。
- h=127.0.0.1 :服务器地址,命令里有2个ip,第一次出现的是M的地址,第2次是Slave的地址。
- u=root :帐号。
- p=123456 :密码。
- --print :打印,但不执行命令。
- --execute :执行命令。
执行数据修复:
[root@zk-kafka-n4 soft]# pt-table-sync --execute --replicate=test1.checksums --databases=test1 h=192.168.3.68,u=percona_tk,p=123456,P=3306 h=192.168.3.67,u=percona_tk,p=123456,P=3306 --no-check-slave --print
DELETE FROM `test1`.`test` WHERE `sdf`='888' LIMIT 1 /*percona-toolkit src_db:test1 src_tbl:test src_dsn:P=3306,h=192.168.3.68,p=...,u=percona_tk dst_db:test1 dst_tbl:test dst_dsn:P=3306,h=zk-kafka-n3,p=...,u=percona_tk lock:1 transaction:1 changing_src:test1.checksums replicate:test1.checksums bidirectional:0 pid:16276 user:root host:zk-kafka-n4*/;
DELETE FROM `test1`.`test` WHERE `sdf`='990' LIMIT 1 /*percona-toolkit src_db:test1 src_tbl:test src_dsn:P=3306,h=192.168.3.68,p=...,u=percona_tk dst_db:test1 dst_tbl:test dst_dsn:P=3306,h=zk-kafka-n3,p=...,u=percona_tk lock:1 transaction:1 changing_src:test1.checksums replicate:test1.checksums bidirectional:0 pid:16276 user:root host:zk-kafka-n4*/;
检查修复结果:
[root@zk-kafka-n4 soft]# pt-table-checksum --no-check-binlog-format --replicate=test1.checksums --databases=test1 h=127.0.0.1,u=percona_tk,p=123456,P=3306 --recursion-method=processlist
Checking if all tables can be checksummed ...
Starting checksum ...
TS ERRORS DIFFS ROWS DIFF_ROWS CHUNKS SKIPPED TIME TABLE
06-21T17:34:17 0 0 6 0 1 0 0.257 test1.test
数据破坏测试,在从机插入3条数据:
检测如下:
[root@zk-kafka-n4 soft]# pt-table-checksum --no-check-binlog-format --replicate=test1.checksums --databases=test1 h=127.0.0.1,u=percona_tk,p=123456,P=3306 --recursion-method=processlist
Checking if all tables can be checksummed ...
Starting checksum ...
TS ERRORS DIFFS ROWS DIFF_ROWS CHUNKS SKIPPED TIME TABLE
06-21T17:41:17 0 1 8 3 1 0 0.259 test1.test
检测发现3条数据异常。
通过slave1-192.168.3.67上的所有数据和主库是同步的(建议用此方式修复,不需要输入master信息),对从机比对主机进行修复(以master数据为参照对象)
[root@zk-kafka-n4 soft]# pt-table-sync --execute --sync-to-master --databases=test1 h=192.168.3.67,u=percona_tk,p=123456,P=3306 --no-check-slave --print
REPLACE INTO `test1`.`checksums`(`db`, `tbl`, `chunk`, `chunk_time`, `chunk_index`, `lower_boundary`, `upper_boundary`, `this_crc`, `this_cnt`, `master_crc`, `master_cnt`, `ts`) VALUES ('test1', 'test', '1', 0.000956, NULL, NULL, NULL, 'e31529f0', '8', 'e31529f0', '8', '2018-06-21 17:41:17') /*percona-toolkit src_db:test1 src_tbl:checksums src_dsn:P=3306,h=192.168.3.68,p=...,u=percona_tk dst_db:test1 dst_tbl:checksums dst_dsn:P=3306,h=192.168.3.67,p=...,u=percona_tk lock:1 transaction:1 changing_src:1 replicate:0 bidirectional:0 pid:16298 user:root host:zk-kafka-n4*/;
DELETE FROM `test1`.`test` WHERE `sdf`='11234' LIMIT 1 /*percona-toolkit src_db:test1 src_tbl:test src_dsn:P=3306,h=192.168.3.68,p=...,u=percona_tk dst_db:test1 dst_tbl:test dst_dsn:P=3306,h=192.168.3.67,p=...,u=percona_tk lock:1 transaction:1 changing_src:1 replicate:0 bidirectional:0 pid:16298 user:root host:zk-kafka-n4*/;
DELETE FROM `test1`.`test` WHERE `sdf`='5655' LIMIT 1 /*percona-toolkit src_db:test1 src_tbl:test src_dsn:P=3306,h=192.168.3.68,p=...,u=percona_tk dst_db:test1 dst_tbl:test dst_dsn:P=3306,h=192.168.3.67,p=...,u=percona_tk lock:1 transaction:1 changing_src:1 replicate:0 bidirectional:0 pid:16298 user:root host:zk-kafka-n4*/;
DELETE FROM `test1`.`test` WHERE `sdf`='7744' LIMIT 1 /*percona-toolkit src_db:test1 src_tbl:test src_dsn:P=3306,h=192.168.3.68,p=...,u=percona_tk dst_db:test1 dst_tbl:test dst_dsn:P=3306,h=192.168.3.67,p=...,u=percona_tk lock:1 transaction:1 changing_src:1 replicate:0 bidirectional:0 pid:16298 user:root host:zk-kafka-n4*/;
修复后再检测下,看检测结果是否修复:
[root@zk-kafka-n4 soft]# pt-table-checksum --no-check-binlog-format --replicate=test1.checksums --databases=test1 h=127.0.0.1,u=percona_tk,p=123456,P=3306 --recursion-method=processlist
Checking if all tables can be checksummed ...
Starting checksum ...
TS ERRORS DIFFS ROWS DIFF_ROWS CHUNKS SKIPPED TIME TABLE
06-21T17:42:42 0 0 8 0 1 0 0.016 test1.test
显示无异常数据