使用percona-toolkit工具校验和修复MySQL数据库主从不一致问题
1、前言
相信很多人的线上都搭建了MySQL主从这样的框架,很多人只监控MySQL的从服务器Slave_IO和Slave_SQL这两个线程是否为YES,还有 Seconds_Behind_Master延迟大不大之类的一些信息。但他们是否定期的去检查MySQL主服务器的数据和从服务器的数据是否一致呢,数据一致性才是最重要的,有人很好奇的问,如果数据不一致,就肯定没有两个YES的出现啦,我想说,不一定的,因为当slave出现错误时,可以通过SET GLOBAL sql_slave_skip_counter = N来跳过错误,还有可以通过选项--slave-skip-errors=[error_code]来跳过错误代码,这样处理后Slave_IO和Slave_SQL状态依然为YES,但这个时候,数据可能就跟主库不一致了。下面和大家学习一个很不错的工具pt-table-checksum
percona-toolkit是用perl开发的一系列的mysql工具集;
pt-table-checksum
是Percona-Toolkit
的组件之一,用于检测MySQL主、从库的数据是否一致。其原理是在主库执行基于statement的sql语句来生成主库数据块的checksum,把相同的sql语句传递到从库执行,并在从库上计算相同数据块的checksum,最后,比较主从库上相同数据块的checksum值,由此判断主从数据是否一致。检测过程根据唯一索引将表按row切分为块(chunk),以为单位计算,可以避免锁表。检测时会自动判断复制延迟、 master的负载, 超过阀值后会自动将检测暂停,减小对线上服务的影响。
pt-table-checksum
默认情况下可以应对绝大部分场景,官方说,即使上千个库、上万亿的行,它依然可以很好的工作,这源自于设计很简单,一次检查一个表,不需要太多的内存和多余的操作;必要时,pt-table-checksum
会根据服务器负载动态改变chunk
大小,减少从库的延迟。
为了减少对数据库的干预,pt-table-checksum
还会自动侦测并连接到从库,当然如果失败,可以指定--recursion-method
选项来告诉从库在哪里。它的易用性还体现在,复制若有延迟,在从库checksum
会暂停直到赶上主库的计算时间点(也通过选项--设定一个可容忍的延迟最大值,超过这个值也认为不一致)。
- 为了保证主数据库服务的安全,该工具实现了许多保护措施:
- 自动设置 innodb_lock_wait_timeout 为1s,避免引起
- 默认当数据库有25个以上的并发查询时,pt-table-checksum会暂停。可以设置 --max-load 选项来设置这个阀值
- 当用 Ctrl+C 停止任务后,工具会正常的完成当前 chunk 检测,下次使用 --resume 选项启动可以恢复继续下一个 chunk
2、工作过程
- 1、连接到主库:pt工具连接到主库,然后自动发现主库的所有从库。默认采用show full processlist来查找从库,但是这只有在主从实例端口相同的情况下才有效。
- 3、查找主库或者从库是否有复制过滤规则:这是为了安全而默认检查的选项。你可以关闭这个检查,但是这可能导致checksum的sql语句要么不会同步到从库,要么到了从库发现从库没有要被checksum的表,这都会导致从库同步卡库。
- 5、开始获取表,一个个的计算。
- 6、如果是表的第一个chunk,那么chunk-size一般为1000;如果不是表的第一个chunk,那么采用19步中分析出的结果。
- 7、检查表结构,进行数据类型转换等,生成checksum的sql语句。
- 8、根据表上的索引和数据的分布,选择最合适的split表的方法。
- 9、开始checksum表。
- 10、默认在chunk一个表之前,先删除上次这个表相关的计算结果。除非–resume。
- 14、根据explain的结果,判断chunk的size是否超过了你定义的chunk-size的上限。如果超过了,为了不影响线上性能,这个chunk将被忽略。
- 15、把要checksum的行加上for update锁,并计算。
- 17-18、把计算结果存储到master_crc master_count列中。
- 19、调整下一个chunk的大小。
- 20、等待从库追上主库。如果没有延迟备份的从库在运行,最好检查所有的从库,如果发现延迟最大的从库延迟超过max-lag秒,pt工具在这里将暂停。
- 21、如果发现主库的max-load超过某个阈值,pt工具在这里将暂停。
- 22、继续下一个chunk,直到这个table被chunk完毕。
- 23-24、等待从库执行完checksum,便于生成汇总的统计结果。每个表汇总并统计一次。
- 25-26、循环每个表,直到结束。
校验结束后,在每个从库上,执行如下的sql语句即可看到是否有主从不一致发生:
select * from percona.checksums where master_cnt <> this_cnt OR master_crc <> this_crc OR
ISNULL(master_crc) <> ISNULL(this_crc) \G
3、环境
IP | Port | 主机名 | 作用 |
192.168.1.101 | 3306 | node1 | master |
192.168.1.102 | 3306 | node2 | slave |
- 注意事项:
- 为了减少不必要的麻烦,确保你的 ptuser@'xxx' 用户能同时登陆主库和从库;
- 只能指定一个host,必须为主库的IP;
- 在检查时会向表加S锁;
- 如果master和slave的binlog日志不是STATEMENT格式,要用--no-check-binlog-format选项
- 运行之前需要从库的同步IO和SQL进程是YES状态。
- 表要有主键索引或唯一键索引
4、下载
选择软件版本:Version,一般默认最新版即可;
选择系统版本:Software,也可以源码编译;我的CentOS6
系统架构:Hardware;我的64位;
5、安装
yum install percona-toolkit-3.0.13-1.el6.x86_64.rpm -y
yum install percona-toolkit-debuginfo-3.0.13-1.el6.x86_64.rpm -y
- CentOS6.*依赖:
perl-DBD-MySQL
perl-DBI
perl-IO-Socket-SSL
perl-Net-LibIDN
perl-Net-SSLeay
perl-Time-HiRes
- CentOS7.*依赖:
perl-Compress-Raw-Bzip2
perl-Compress-Raw-Zlib
perl-DBD-MySQL
perl-DBI
perl-Digest
perl-Digest-MD5
perl-IO-Compress
perl-IO-Socket-IP
perl-IO-Socket-SSL
perl-Mozilla-CA
perl-Net-Daemon
perl-Net-LibIDN
perl-Net-SSLeay
perl-PlRPC
查看安装的文件:
[root@node1 ~]# rpm -ql percona-toolkit
/usr/bin/pt-align
/usr/bin/pt-archiver
/usr/bin/pt-config-diff
/usr/bin/pt-deadlock-logger
/usr/bin/pt-diskstats
/usr/bin/pt-duplicate-key-checker
/usr/bin/pt-fifo-split
/usr/bin/pt-find
/usr/bin/pt-fingerprint
/usr/bin/pt-fk-error-logger
/usr/bin/pt-heartbeat
/usr/bin/pt-index-usage
/usr/bin/pt-ioprofile
/usr/bin/pt-kill
/usr/bin/pt-mext
/usr/bin/pt-mongodb-query-digest
/usr/bin/pt-mongodb-summary
/usr/bin/pt-mysql-summary
/usr/bin/pt-online-schema-change
/usr/bin/pt-pmp
/usr/bin/pt-query-digest
/usr/bin/pt-secure-collect
/usr/bin/pt-show-grants
/usr/bin/pt-sift
/usr/bin/pt-slave-delay
/usr/bin/pt-slave-find
/usr/bin/pt-slave-restart
/usr/bin/pt-stalk
/usr/bin/pt-summary
/usr/bin/pt-table-checksum # 校验数据一致性;
/usr/bin/pt-table-sync # 修复不一致数据;
/usr/bin/pt-table-usage
/usr/bin/pt-upgrade
/usr/bin/pt-variable-advisor
/usr/bin/pt-visual-explain
... ...
[root@node1 ~]#
6、创建演示数据
6.1、主库master:
root@node1 10:56: [(none)]> create database pt_check;
Query OK, 1 row affected (0.04 sec)
root@node1 10:57: [(none)]> use pt_check
Database changed
root@node1 10:58: [pt_check]> create table test1(id int auto_increment primary key,name varchar(20) not null);
Query OK, 0 rows affected (0.86 sec)
root@node1 11:03: [pt_check]> insert into test1 values(null,'will');
Query OK, 1 row affected (0.00 sec)
root@node1 11:03: [pt_check]> insert into test1 values(null,'jim');
Query OK, 1 row affected (0.00 sec)
root@node1 11:03: [pt_check]> insert into test1 values(null,'tom');
Query OK, 1 row affected (0.05 sec)
root@node1 11:03: [pt_check]> select * from pt_check.test1;
+----+------+
| id | name |
+----+------+
| 1 | will |
| 2 | jim |
| 3 | tom |
+----+------+
3 rows in set (0.00 sec)
root@node1 11:04: [pt_check]>
6.2、从库slave:
root@node1 11:03: [pt_check]> select * from pt_check.test1;
+----+------+
| id | name |
+----+------+
| 1 | will |
| 2 | jim |
| 3 | tom |
+----+------+
3 rows in set (0.00 sec)
root@node1 11:04: [pt_check]> delete from pt_check.test1 where id='2';
Query OK, 1 row affected (0.02 sec)
root@node2 12:23: [(none)]> select * from pt_check.test1;
+----+------+
| id | name |
+----+------+
| 1 | will |
| 3 | tom |
+----+------+
2 rows in set (0.00 sec)
root@node2 12:23: [(none)]>
6.3、创建校验用户
- master
root@node1 12:28: [pt_check]> GRANT CREATE,INSERT,SELECT,DELETE,UPDATE,LOCK TABLES,PROCESS,SUPER,REPLICATION SLAVE ON *.* TO 'ptuser'@'192.168.1.101' IDENTIFIED BY '123456';
Query OK, 0 rows affected (0.00 sec)
root@node1 12:29: [pt_check]> flush privileges;
Query OK, 0 rows affected (0.00 sec)
root@node1 12:29: [pt_check]> select Host,User from mysql.user;
+----------------+---------------+
| Host | User |
+----------------+---------------+
| localhost | root |
| localhost | mysql.session |
| localhost | mysql.sys |
| 172.16.156.% | rep |
| % | java |
| 192.168.1.101 | ptuser |
+----------------+---------------+
9 rows in set (0.00 sec)
root@node1 12:29: [pt_check]>
- slave
root@node2 12:48: [(none)]> select Host,User from mysql.user;
+----------------+---------------+
| Host | User |
+----------------+---------------+
| localhost | root |
| localhost | mysql.session |
| localhost | mysql.sys |
| 172.16.156.% | rep |
| % | java |
| 192.168.1.101 | ptuser |
+----------------+---------------+
8 rows in set (0.00 sec)
root@node2 12:48: [(none)]>
- 权限解释:
- select //查看所有库的表,原理可加 explain选项查看
- process //自动发现从库信息,show processlist
- super //set binlog_format='statement'
- replication slave //show slave hosts
7、pt-table-checksum校验
7.1、pt-table-checksum参数解释
- --replicate-check:执行完 checksum 查询在percona.checksums表中,不一定马上查看结果呀 —— yes则马上比较chunk的crc32值并输出DIFFS列,否则不输出。默认yes,如果指定为--noreplicate-check,一般后续使用下面的--replicate-check-only去输出DIFF结果。
- --nocheck-replication-filters :不检查复制过滤器,建议启用。后面可以用--databases来指定需要检查的数据库。
- --no-check-binlog-format : 不检测日志格式。这个选项对于 ROW 模式的复制很重要,因为pt-table-checksum会在 Master和Slave 上设置binlog_format=STATEMENT(确保从库也会执行 checksum SQL),MySQL限制从库是无法设置的,所以假如行复制从库,再作为主库复制出新从库时(A->B->C),B的checksums数据将无法传输。(没验证)
- --replicate-check-only :不在主从库做 checksum 查询,只在原有 percona.checksums 表中查询结果,并输出数据不一致的信息。周期性的检测一致性时可能用到。
- --replicate= :把checksum的信息写入到指定表中,如果没有指定,默认是 percona.checksums ;建议直接写到被检查的数据库当中。
- --databases=,-d :要检查的数据库,逗号分隔;--databases-regex 正则匹配要检测的数据库,--ignore-databases[-regex]忽略检查的库。Filter选项。
- --tables=,-t
:要检查的表,逗号分隔。如果要检查的表分布在不同的db中,可以用--tables=dbname1.table1,dbnamd2.table2的形式。同理有--tables-regex,--ignore-tables,--ignore-tables-regex。--replicate指定的checksum表始终会被过滤。 - --tables= :指定需要被检查的表,多个用逗号隔开
- h=192.168.1.101 :Master的地址
- u=ptuser :用户名
- p=123456 :密码
- P=3306 :端口
- --create-replicate-table 选项会自动创建 percona.checksums 表,但也意味着赋予额外的 CREATE TABLE权限给 percona_tk@'xxx' 用户。默认yes
- --no-check-replication-filters 表示不需要检查 Master 配置里是否指定了 Filter。 默认会检查,如果配置了 Filter,如 replicate_do_db,replicate-wild-ignore-table,binlog_ignore_db 等,在从库checksum就与遇到表不存在而报错退出,所以官方默认是yes(--check-replication-filters)但我们实际在检测中时指定--databases=,所以就不存在这个问题,干脆不检测;
- --empty-replicate-table:每个表checksum开始前,清空它之前的检测数据(不影响其它表的checksum数据),默认yes。当然如果使用--resume启动检测数据不会清空。当启用--noempty-replicate-table即不清空时,不计算计算chunk,只计算。
- --recursion-method:发现从库的方式。pt-table-checksum 默认可以在主库的 processlist 中找到从库复制进程,从而识别出有哪些从库,但如果使用是非标准3306端口,会导致找不到从库信息。此时就会自动采用host方式,但需要提前在从库 my.cnf 里面配置report_host、report_port信息,如:
report_host = MASTER_HOST
report_port = 13306
最终极的办法是dsn,dsn指定的是某个表(如 percona.dsns ),表行记录是改主库的(多个)从库的连接信息。适用以下任一情形:
- 主库不能自动发现从库
- 不想在从库添加额外配置(因为要重启)
- 主从检测连接用户信息不一样
- 多个从库时只想验证指定从库的一致
我比较倾向使用DSN的方式。这个dsns表只需要在执行 pt-table-checksum 命令的服务器上能够访问到就行。这里纠正一个认识,网上很多人说 pt-table-checksum 要在主库上执行,其实不是的,我的mysql实例比较多,只需在某一台服务器上安装percona-toolkit,这台服务能够同时访问主库和从库就行了。具体用法见后面实例。
7.2、在主库上执行数据检查命令
[root@node1 ~]# pt-table-checksum --nocheck-replication-filters --replicate=test.checksums --databases=pt_check --tables=test1 h=192.168.1.101,u=ptuser,p=123456,P=3306
Checking if all tables can be checksummed ...
Starting checksum ...
Replica node2 has binlog_format ROW which could cause pt-table-checksum to break replication. Please read "Replicas using row-based replication" in the LIMITATIONS section of the tool's documentation. If you understand the risks, specify --no-check-binlog-format to disable this check.
[root@node1 ~]#
从库node2的bbinlog日志为ROW,这可能导致pt-table-checksum中断复制。可以指定--no-check-binlog-format以禁用此检查。
[root@node1 ~]# pt-table-checksum --nocheck-replication-filters --replicate=test.checksums --databases=pt_check --tables=test1 h=192.168.1.101,u=ptuser,p=123456,P=3306 --no-check-binlog-format
Checking if all tables can be checksummed ...
Starting checksum ...
TS ERRORS DIFFS ROWS DIFF_ROWS CHUNKS SKIPPED TIME TABLE
02-19T18:23:22 0 1 3 0 1 0 0.045 pt_check.test1
[root@node1 ~]#
表示说明:
- TS :完成检查的时间。
- ERRORS :检查时候发生错误和警告的数量。
- DIFFS :不一致的chunk数量。当指定 --no-replicate-check 即检查完但不立即输出结果时,会一直为0;当指定 --replicate-check-only 即不检查只从checksums表中计算crc32,且只显示不一致的信息(毕竟输出的大部分应该是一致的,容易造成干扰)。
- ROWS :比对的表行数。
- CHUNKS :被划分到表中的块的数目。
- SKIPPED:由于错误或警告或过大,则跳过块的数目。
- TIME :执行的时间。
- TABLE :被检查的表名。
看到已经检查出主从数据有不一致了,DIFFS下的值为1,怎么不一致呢? 通过指定--replicate=test.checksums 参数,就说明把检查信息都写到了checksums表中
7.3、master的test.checksums表:
root@node1 09:19: [(none)]> select * from test.checksums \G
*************************** 1. row ***************************
db: pt_check
tbl: test1
chunk: 1
chunk_time: 0.005212
chunk_index: NULL
lower_boundary: NULL
upper_boundary: NULL
this_crc: b9a54161
this_cnt: 3 # 本机 3行数据
master_crc: b9a54161
master_cnt: 3 # master 3行数据
ts: 2019-02-20 09:18:01
1 row in set (0.00 sec)
root@node1 09:19: [(none)]>
7.4、slave的test.checksums表:
root@node2 09:20: [(none)]> select * from test.checksums \G
*************************** 1. row ***************************
db: pt_check
tbl: test1
chunk: 1
chunk_time: 0.005212
chunk_index: NULL
lower_boundary: NULL
upper_boundary: NULL
this_crc: d49ddeb7
this_cnt: 2 # 本机 2行数据
master_crc: b9a54161
master_cnt: 3 # master 3行数据
ts: 2019-02-20 09:18:01
1 row in set (0.01 sec)
root@node2 09:20: [(none)]>
8、pt-table-sync修复
8.1、打印数据的不同之处
master库用pt-table-sync命令和--print选项打印出master下的check_sum.test1和slave库的check_sum.test1的不一致的数据,如下:
[root@node1 ~]# pt-table-sync --replicate=test.checksums h=192.168.1.101,u=ptuser,p=123456,P=3306 h=192.168.1.102,u=ptuser,p=123456,P=3306 --print
REPLACE INTO `pt_check`.`test1`(`id`, `name`) VALUES ('2', 'jim') /*percona-toolkit src_db:pt_check src_tbl:test1 src_dsn:P=3306,h=192.168.1.101,p=...,u=ptuser dst_db:pt_check dst_tbl:test1dst_dsn:P=3306,h=node2,p=...,u=ptuser lock:1 transaction:1 changing_src:test.checksums replicate:test.checksums bidirectional:0 pid:20377 user:root host:node1*/;
[root@node1 ~]#
- pt-table-sync参数说明:
--replicate= :指定通过pt-table-checksum得到的表.
--databases= : 指定执行同步的数据库,多个用逗号隔开。
--tables= :指定执行同步的表,多个用逗号隔开。
--sync-to-master :指定一个DSN,即从的IP,他会通过show processlist或show slave status 去自动的找主。
h=127.0.0.1 :服务器地址,命令里有2个ip,第一次出现的是Master的地址,第2次是Slave的地址。
u=root :帐号。
p=123456 :密码。
--print :打印,但不执行命令。
--execute :执行命令。
8.2、pt-table-sync修复不同数据
接下的操作就是把slave上少的数据,从master同步过去(master操作);通过(--execute),让它们数据保持一致性:
[root@node1 ~]# pt-table-sync --replicate=test.checksums h=192.168.1.101,u=ptuser,p=123456,P=3306 h=192.168.1.102,u=ptuser,p=123456,P=3306 --execute
9、验证
9.1、使用pt-table-checksum重新校验:
[root@node1 ~]# pt-table-checksum --nocheck-replication-filters --replicate=test.checksums --databases=pt_check --tables=test1 h=192.168.1.101,u=ptuser,p=123456,P=3306 --no-check-binlog-format
Checking if all tables can be checksummed ...
Starting checksum ...
TS ERRORS DIFFS ROWS DIFF_ROWS CHUNKS SKIPPED TIME TABLE
02-20T10:03:02 0 0 3 0 1 0 0.083 pt_check.test1
[root@node1 ~]#
可以看到再次检查的时候,DIFFS已经是0了;
9.2、主库master:
root@node1 10:05: [(none)]> select * from pt_check.test1;
+----+------+
| id | name |
+----+------+
| 1 | will |
| 2 | jim |
| 3 | tom |
+----+------+
3 rows in set (0.00 sec)
root@node1 10:05: [(none)]>
9.3、从库slave:
root@node2 10:02: [(none)]> select * from pt_check.test1;
+----+------+
| id | name |
+----+------+
| 1 | will |
| 2 | jim |
| 3 | tom |
+----+------+
3 rows in set (0.00 sec)
root@node2 10:05: [(none)]>
已经跟master上的数据一致了。
10、报错
10.1、用户权限问题
没有创建CREATE表的权限;
[root@node1 ~]# pt-table-checksum --nocheck-replication-filters --replicate=test.checksums --databases=pt_check --tables=test1 h=192.168.1.101,u=ptuser,p=123456,P=3306 --no-check-binlog-format
Checking if all tables can be checksummed ...
Starting checksum ...
02-19T18:08:22 --create-replicate-table failed: DBD::mysql::db do failed: CREATE command denied to user 'ptuser'@'node1' for table 'checksums' [for Statement " CREATE TABLE IF NOT EXISTS `test`.`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 DEFAULT CHARSET=utf8"] at /usr/bin/pt-table-checksum line 12272.
02-19T18:08:22 --replicate table checksums does not exist and it cannot be created automatically. You need to create the table.
[root@node1 ~]#
10.2、Diffs cannot be detected because no slaves were found
不能自动找到从库,确认processlist或host或dsns方式用对了。
10.3、Cannot connect to h=slave1.*.com,p=...,u=percona_user
可以在pt-table-checksum命令前加PTDEBUG=1来看详细的执行过程,如端口、用户名、权限错误。
10.4、Waiting for the --replicate table to replicate to XXX
问题出在 percona.checksums 表在从库不存在,根本原因是没有从主库同步过来,所以看一下从库是否延迟严重。
10.5、Pausing because Threads_running=25
反复打印出类似上面停止检查的信息。这是因为当前数据库正在运行的线程数大于默认25,pt-table-checksum 为了减少对库的压力暂停检查了。等数据库压力过了就好了,或者也可以直接 Ctrl+C 终端,下一次加上--resume继续执行,或者加大--max-load=值。
10.6、字符集问题
转载于:https://blog.51cto.com/moerjinrong/2352317