在主库执行: mysql>GRANT SELECT, PROCESS, SUPER, REPLICATION SLAVE,CREATE,DELETE,INSERT,UPDATE ON . TO 'USER'@'MASTER_HOST' identified by 'PASSWORD'; 注:创建用户,这些权限都是必须的,否则后续执行时会报错,当然,如果不想授予这么多权限,那就需要把权限对应的活先自己干了或者直接在命令行指定,比如如果不想设create权限的话,需要自己指定库和表(具体参看下面的参数介绍:--replicate) shell> ./pt-table-checksum --host='master_host' --user='user' --password='password' --port='port' --databases=databases --tables=tables --recursion-method=processlist 注: (1)在有些情况下,recursion-method如果不设会报错:Diffs cannot be detected because no slaves were found. 其参数有四:processlist/hosts/dsn=DSN/no,用来决定查找slave的方式是show full processlist还是show slave hosts还是命令行直接指定还是压根就不准备找从库,具体见下面参数介绍 (2)主从的端口必须一致,如果不一致就需要用DSN方法进行指定,否则会报找不到从库的错误,如果能连到从库服务器但没有指定端口,默认会寻找3306端口 (3)被检查的主从binlog_format必须为statement,如果不是statement-based,那就添加参数--no-check-binlog-format来避开binlog格式检查 (4)检查结果会输出到默认建立的percona库中的checksums表中,并会输出统计信息到屏幕,diffs列展示主从数据不一致的块的数目,如果都是0,恭喜,数据是一致的

一. 运行原理

pt-table-checksum运行在主库上,通过show processlist或show slave hosts或DSN方式来确定从库并连接,默认使用crc32算法来进行数据校验,该工具之所以需要把binlog设置为statement格式,是因为该工具能得出主从是否一致所依赖的就是statement基础上同样的SQL语句在主从库上各自的执行结果,主库进行检查后sql语句传给从库,从库执行一遍后,也得到自己的结果,执行语句是:

SELECT COUNT(*) AS cnt, COALESCE(LOWER(CONV(BIT_XOR(CAST(CRC32(CONCAT_WS('#', 各种列名)) AS UNSIGNED)), 10, 16)), 0) AS crc FROM database.table FORCE INDEX(PRIMARY) WHERE ((id >= '1')) AND ((id <= '1000'))

注: where的条件是根据系统繁忙程度计算出的要执行的范围 cnt是目前检查的块包括的行数,unsigned是计算出的该块数据的校验值

如果主库和从库得出的这两个值都是一样的,那数据就是一致的,如果不一样,那就主从不一致,当然,字符集、浮点数之类的问题需要提前规避,以免错判

工具将主从各自得到的结果处理后放到checksums表中并呈现一些结果在屏幕输出中,work over

二. 安全性保障

pt-table-checksum采用了很多措施来保证检查过程中的安全性,默认参数是可以保障使用安全的,不过参数可以配置,所以需要详细了解参数的功能后再进行更改,否则最好采用默认

先了解一下工具在执行过程中做了些什么:

主库

SET SESSION innodb_lock_wait_timeout=1 /只针对innodb表/ SET SESSION wait_timeout=10000 SET SQL_MODE='NO_AUTO_VALUE_ON_ZERO,NO_ENGINE_SUBSTITUTION' SET @@binlog_format = 'STATEMENT' SET SESSION TRANSACTION ISOLATION LEVEL REPEATABLE READ SET @@SQL_QUOTE_SHOW_CREATE = 1 /保证检查安全的相关设置/

CREATE DATABASE IF NOT EXISTS percona CREATE TABLE IF NOT EXISTS percona.checksums /创建校验值存放的库和表/

SHOW GLOBAL STATUS LIKE 'Threads_running' /检查系统运行情况/

SHOW DATABASES SHOW TABLES FROM database USE database SHOW CREATE TABLE database.a EXPLAIN SELECT * FROM database.a WHERE 1=1 SELECT /*!40001 SQL_NO_CACHE / id FROM database.a FORCE INDEX(PRIMARY) ORDER BY id LIMIT 1 /first lower boundary/ SELECT /!40001 SQL_NO_CACHE / id FROM database.a FORCE INDEX (PRIMARY) WHERE id IS NOT NULL ORDER BY id LIMIT 1 /key_len/ EXPLAIN SELECT /!40001 SQL_NO_CACHE */ * FROM database.a FORCE INDEX (PRIMARY) WHERE id >= '1' /key_len/

/每次用use database来确定数据库并依次只选择一个表进行详细数据量分析/

USE percona DELETE FROM percona.checksums WHERE db = 'database' AND tbl = 'a'

/避免之前有过检查并保存有该表的检查信息,将对应信息删掉/

USE database EXPLAIN SELECT /*!40001 SQL_NO_CACHE / id FROM database.a FORCE INDEX(PRIMARY) WHERE ((id >= '1')) ORDER BY id LIMIT 999, 2 /next chunk boundary/ SELECT /!40001 SQL_NO_CACHE */ id FROM database.a FORCE INDEX(PRIMARY) WHERE ((id >= '1')) ORDER BY id LIMIT 999, 2 /next chunk boundary/

/每次检查表时,第一个块的行数固定为1000,之后会根据系统繁忙程度计算出在规定时间内能处理的行数来确定为一个chunk,默认时间为0.5秒,可以更改/

EXPLAIN SELECT COUNT() AS cnt, COALESCE(LOWER(CONV(BIT_XOR(CAST(CRC32(CONCAT_WS('#', id, name, type)) AS UNSIGNED)), 10, 16)), 0) AS crc FROM database.a FORCE INDEX(PRIMARY) WHERE ((id >= '1')) AND ((id <= '1000')) /explain checksum chunk/ REPLACE INTO percona.checksums (db, tbl, chunk, chunk_index, lower_boundary, upper_boundary, this_cnt, this_crc) SELECT 'database', 'a', '1', ' PRIMARY', '1', '1000', COUNT() AS cnt, COALESCE(LOWER(CONV(BIT_XOR(CAST(CRC32(CONCAT_WS('#', id, name, type)) AS UNSIGNED)), 10, 16)), 0) AS crc FROM database.a FORCE INDEX(PRIMARY) WHERE ((id >= '1')) AND ((id <= '1000')) /checksum chunk/

/explain下确定下执行计划,然后开始真正的checksum工作,是用 REPLACE..SELECT语句计算数据情况并将结果插入到checksums表中/

SHOW WARNINGS UPDATE percona.checksums SET chunk_time = '0.003725', master_crc = 'a9bd6d97', master_cnt = '1000' WHERE db = 'database' AND tbl = 'a' AND chunk='1'

/检查是否有警告,进一步完善checksums表中的检查信息/

一个块的检查结束,开始下一个块或下一个表

从库

跟主库类似,少了一些设置及explain过程,但多了一个show slave status的检查,来辅助工具判断从库连接和延迟等情况,来确定检查是否继续,暂停还是退出

所有检查执行完后会有这样一个语句来报出检查结果

SELECT CONCAT(db, '.', tbl) AS table, chunk, chunk_index, lower_boundary, upper_boundary, COALESCE(this_cnt-master_cnt, 0) AS cnt_diff, COALESCE(this_crc <> master_crc OR ISNULL(master_crc) <> ISNULL(this_crc), 0) AS crc_diff, this_cnt, master_cnt, this_crc, master_crc FROM percona.checksums WHERE (master_cnt <> this_cnt OR master_crc <> this_crc OR ISNULL(master_crc) <> ISNULL(this_crc)) AND (db='database' AND tbl='a')

pt-table-checksum一次只针对一个表,而且会根据表的大小以及当前系统的繁忙程度,计算出一次检查中能包含的数据行数,来尽量避免对线上服务的影响,如果在执行过程中遇到突发的负载增加,还会自动的将检查停下来等待,所以即使面对成千上万的数据库和表时,它也能顺利的进行检查

在检查过程中,工具会随时对主从连接情况进行检查,如果从库延迟太大,主从复制中断,检查会停下来等待;这里需要注意的是主从复制过滤,因为这种情形下,主从数据库中的库表存在情况不一致,检查过程中的执行语句会与当前的主从复制过程冲突导致主从复制进程失败,所以如果有过滤存在,需要指定参数--no-check-replication-filters

在一个块的数据被检查之前,会先执行explain操作,来确定执行该检查的安全性,如果太大不能在指定时间内完成检查的话就会将该块数据跳过,另外,如果主库上整表的数据特别少或干脆是空表,并不会直接将整表当做一个块去检查,而是会再去从库,确定从库中也是有同样少的数据,避免从库表数据太多却被当成一个块执行造成的从库数据阻塞

另外还有一些安全保护设置,在上面的执行流程中已经列出来了,如设置innodb_lock_wait_timeout=1,如果锁等待超过1S,就放弃此次执行

在执行过程中如果遇到任何异常,可随时中断进程,如kill或CTRL-C,不会造成任何影响,后面想从此次中断继续检查时,简单的采用--resume就可以

三. 参数介绍

  1. 连接主从库的参数:

--host --socket --user --password --pid --port

  1. 确定比较范围的参数

(1) 指定库

--databases / --ignore-databases 要比较的库 / 比较过程中忽略这些库

--databases-regex / --ignore-databases-regex 同上,不过可以用正则匹配

(2) 指定表

--tables / --ignore-tables 要比较的表 / 比较过程中忽略这些表

--tables-regex / --ignore-tables-regex 同上,不过可以用正则匹配

(3) 指定列

--columns / --ignore-columns 要比较的列 / 比较过程中忽略这些列

(4) 直接指定表范围

--where 直接指定表中要比较的范围

(5) 根据引擎选表

--engines / --ignore-engines 比较指定的引擎表 / 比较过程中忽略含有这些引擎的表

  1. 指定连接中断后行为的参数

--resume 如果主从一致性检查中途中断的话,可以用这个参数来使工具从上次中断时检查的最后一个表开始继续检查

--retries 如果在检查过程中有非致命性的中断的话,如被kill或者从库延迟等,指定该参数后,工具会自动尝试重连

  1. 需重点关注的参数

(1) --[no]check-binlog-format

  默认会检查binlog-format,如果不是statment,就会报错退出,想避免该检查可以设置--no-check-binlog-format

(2) --recursion-method

参数有四:processlist/hosts/dsn=DSN/no,默认是processlist,hosts,但最好还是指定一下,建议指定--recursion-method=processlist,no一般不使用

dsn=DSN方法使用时,需要先去库里创建一个表,比如在percona库中建一个dnsn表

建表语句是: 

CREATE TABLE dsns (id int(11) NOT NULL AUTO_INCREMENT,parent_id int(11) DEFAULT NULL,dsn varchar(255) NOT NULL,PRIMARY KEY (id)); 建好后插入主从复制信息数据,如:insert into table dsns(dsn) values(h=slave_host,u=repl_user,p=repl_password,P=port );

然后就可以使用DSN方法了:命令为:--recursion-method dsn=D=percona,t=dsns.

(3) --replicate

用来指定存放计算结果的表名, 默认是percona.checksums,工具会默认自动创建库percona和表checksums并将checksum的检查结果输入到这个表中,如果自己用该参数去指定表的话,表结构必须是:

复制代码 CREATE TABLE 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, PRIMARY KEY (db, tbl, chunk), INDEX ts_db_tbl (ts, db, tbl) ) ENGINE=InnoDB; 复制代码 需要注意的是存储引擎设置,如果检查的表是innodb表,就设置innodb引擎,如果检查的表和checksums表的引擎不一致,如分别是myisam和innodb,会引起复制错误:“different error on master and slave.”!!!

  1. 其他部分参数详述:

(1) --[no]check-replication-filters

 默认在检查到在主从复制过程中有被用..ignore..过滤掉的表,检查会中断并退出,如果想避开这个检查可以设置--no-check-replication-filters

(2) --chunk-index(type: string)

 工具默认在分块时会选取最合适的索引来explain确定chunk的大小,但如果你希望用其他索引来执行,可以用该参数来指定,工具会以FORCE INDEX的形式把指定的索引加进去

(3) --chunk-index-columns(type: int)

 可以用来指定组合索引中使用前几个列来辅助分块

(4) --chunk-size

 直接确定chunk的大小,默认1000行数据,但不建议使用,建议使用--chunk-time代替

(5) --chunk-time

 默认是0.5秒,工具会根据当前系统运行繁忙程度计算出在该指定时间内可以处理的数据行数(即chunk),比较灵活

(6) --[no]empty-replicate-table

 默认yes,每次检查表之前都去把checksums表中已有的该表信息删掉,以利于后续重新插入新检查信息

(7) --float-precision(type: int)

 设置浮点数的四舍五入方式,以避免不同版本间或其他特定情况中,主从间因浮点数四舍五入的方式不同而导致查出不一致,If you specify a value of 2, for example, then the values 1.008 and 1.009 will be rounded to 1.01, and will checksum as equal

(8) --function

 计算checksum值时的函数,默认是CRC32,其他还有FNV1A_64, MURMUR_HASH, SHA1, MD5等

(9) --max-lag

默认1S,主从最大延迟,超过这个延迟时间,就会停下来等待从库同步,确定方法是采用Seconds_Behind_Master的值

(10) --progress

指定后可以按设定的参数将执行过程中的运行情况输出到STDERR,如主从延迟时从库的等待,等待时间等,指定时后跟两个参数值,默认是 "time,30",前一个参数有:percentage, time, or iterations;后一个指定百分比,具体时间或者间隔的数目

四. 结果分析

        TS ERRORS  DIFFS  ROWS  CHUNKS SKIPPED    TIME TABLE

10-20T08:36:50 0 0 200 1 0 0.005 db1.tbl1 10-20T08:36:50 0 0 603 7 0 0.035 db1.tbl2 10-20T08:36:50 0 0 16 1 0 0.003 db2.tbl3 10-20T08:36:50 0 0 600 6 0 0.024 db2.tbl4

复制代码 TS The timestamp (without the year) when the tool finished checksumming the table. ERRORS The number of errors and warnings that occurred while checksumming the table. Errors and warnings are printed to standard error while the table is in progress. DIFFS The number of chunks that differ from the master on one or more replicas. If --no-replicate-check is specified, this column will always have zeros. If --replicate-check-only is specified, then only tables with differences are printed. ROWS The number of rows selected and checksummed from the table. It might be different from the number of rows in the table if you use the –where option. CHUNKS The number of chunks into which the table was divided. SKIPPED The number of chunks that were skipped due to errors or warnings, or because they were oversized. TIME The time elapsed while checksumming the table. TABLE The database and table that was checksummed. 复制代码