使用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、下載
打開官網:https://www.percona.com/downloads/percona-toolkit/LATEST/
選擇軟件版本:Version,一般默認最新版即可;
選擇系統版本:Software,也可以源碼編譯;我的CentOS6
系統架構:Hardware;我的64位;
我的下載為:
https://www.percona.com/downloads/percona-toolkit/3.0.13/binary/redhat/6/x86_64/percona-toolkit-debuginfo-3.0.13-1.el6.x86_64.rpm
https://www.percona.com/downloads/percona-toolkit/3.0.13/binary/redhat/6/x86_64/percona-toolkit-3.0.13-1.el6.x86_64.rpm
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.101,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.101,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 ~]#
11、參考
https://segmentfault.com/a/1190000004309169#articleHeader5
https://www.cnblogs.com/xuanzhi201111/p/4180638.html
使用percona-toolkit工具校驗和修復MySQL數據庫主從不一致問題