一、背景
公司數據中心從託管機房遷移到阿里雲,需要對MySQL遷移(Replication)後的數據一致性進行校驗,但又不能對生產環境使用造成影響,pt-table-checksum成爲了絕佳也是唯一的檢查工具。所以就利用pt-table-checksum工作來檢查主從的一致性,以及通過pt-table-sync如何修復這些不一致的數據。
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會暫停直到趕上主庫的計算時間點(也通過選項–設定一個可容忍的延遲最大值,超過這個值也認爲不一致)。
二、percona-toolkit工具安裝
1)軟件下載:https://www.percona.com/downloads/percona-toolkit
2)安裝該工具依賴的軟件包
$ yum install perl-IO-Socket-SSL perl-DBD-MySQL perl-Time-HiRes perl-TermReadKey perl-IO-Socket-SSL -y
1 |
$ yum install perl-IO-Socket-SSL perl-DBD-MySQL perl-Time-HiRes perl-TermReadKey perl-IO-Socket-SSL -y |
3)軟件安裝
$ yum localinstall percona-toolkit-2.2.18-1.noarch.rpm
1 |
$ yum localinstall percona-toolkit-2.2.18-1.noarch.rpm |
三、pt-table-checksum工具使用
使用方法:
$ pt-table-checksum [OPTIONS] [DSN]
1 |
$ pt-table-checksum [OPTIONS] [DSN] |
pt-table-checksum在主(master)上通過執行校驗的查詢對複製的一致性進行檢查,對比主從的校驗值,從而產生結果。DSN指向的是主的地址,該工具的退出狀態不爲零,如果發現有任何差別,或者如果出現任何警告或錯誤,更多信息請查看官方資料。
下面通過實際的例子來解釋該工具如何使用:
主庫(3306)和從庫(3307)目前主從複製正常運行。
主庫(3306)
mysql> create database test; mysql> CREATE TABLE `tt` ( `id` int(11) NOT NULL AUTO_INCREMENT, `count` int(11) DEFAULT NULL, PRIMARY KEY (`id`) ) ENGINE=InnoDB AUTO_INCREMENT=4 DEFAULT CHARSET=utf8mb4; mysql> insert into test.tt values(1,1); mysql> insert into test.tt values(2,2); mysql> insert into test.tt values(3,3);
1 2 3 4 5 6 7 8 9 10 |
mysql> create database test; mysql> CREATE TABLE `tt` ( `id` int(11) NOT NULL AUTO_INCREMENT, `count` int(11) DEFAULT NULL, PRIMARY KEY (`id`) ) ENGINE=InnoDB AUTO_INCREMENT=4 DEFAULT CHARSET=utf8mb4;
mysql> insert into test.tt values(1,1); mysql> insert into test.tt values(2,2); mysql> insert into test.tt values(3,3); |
從庫(3307)
mysql> select * from test.tt; +----+-------+ | id | count | +----+-------+ | 1 | 1 | | 2 | 2 | | 3 | 3 | +----+-------+ 3 rows in set (0.00 sec)
1 2 3 4 5 6 7 8 9 |
mysql> select * from test.tt; +----+-------+ | id | count | +----+-------+ | 1 | 1 | | 2 | 2 | | 3 | 3 | +----+-------+ 3 rows in set (0.00 sec) |
備庫已經自動複製了主庫的信息,那麼爲了模擬數據不一致性,我們可以往主庫插入幾條數據。
mysql> insert into test.tt values(4,4); Query OK, 1 row affected (0.00 sec) mysql> insert into test.tt values(5,5); Query OK, 1 row affected (0.00 sec)
1 2 3 4 5 |
mysql> insert into test.tt values(4,4); Query OK, 1 row affected (0.00 sec)
mysql> insert into test.tt values(5,5); Query OK, 1 row affected (0.00 sec) |
很明顯主從數據不一致,那麼我們使用工具來檢測。在檢測之前需要有一個前提條件,如下:
1、運行之前需要從庫的同步IO和SQL進程是YES狀態,因爲從庫要同步主庫的check信息。
2、運行時只能指定一個host,必須爲主庫的IP。
3、在檢查時會向表加S鎖。
4、在兩個庫上都創建一個相同的用戶和密碼(爲了方便後面的pt-table-checksum運行)。
mysql> GRANT all ON *.* TO 'root'@'%' IDENTIFIED BY '123456'; Query OK, 0 rows affected (0.00 sec) mysql> flush privileges; Query OK, 0 rows affected (0.00 sec)
1 2 3 4 5 |
mysql> GRANT all ON *.* TO 'root'@'%' IDENTIFIED BY '123456'; Query OK, 0 rows affected (0.00 sec)
mysql> flush privileges; Query OK, 0 rows affected (0.00 sec) |
下面開始在主庫上運行pt-table-checksum工具。
$ pt-table-checksum --nocheck-replication-filters --no-check-binlog-format --replicate=test.checksums --databases=test --tables=tt h=10.0.60.143,u=root,p='123456',P=3306 Diffs cannot be detected because no slaves were found. Please read the --recursion-method documentation for information.
1 2 |
$ pt-table-checksum --nocheck-replication-filters --no-check-binlog-format --replicate=test.checksums --databases=test --tables=tt h=10.0.60.143,u=root,p='123456',P=3306 Diffs cannot be detected because no slaves were found. Please read the --recursion-method documentation for information. |
參數的意思:
--nocheck-replication-filters #不檢查複製過濾器,建議啓用。後面可以用--databases來指定需要檢查的數據庫; --no-check-binlog-format #不檢查複製的binlog模式,要是binlog模式是ROW,則會報錯; --replicate-check-only #只顯示不同步的信息; --replicate=test.checksums #把checksum的信息寫入到指定表中,建議直接寫到被檢查的數據庫當中; --databases=test #指定需要被檢查的數據庫,多個則用逗號隔開; --tables=tt #指定需要被檢查的表,多個用逗號隔開; --socket= #可指定Master的socket; h=127.0.0.1 #Master的地址; u=root #用戶名(Master和Slave可共用); p=123456 #密碼(Master和Slave可共用); P=3306 #Master的端口;
1 2 3 4 5 6 7 8 9 10 11 |
--nocheck-replication-filters #不檢查複製過濾器,建議啓用。後面可以用--databases來指定需要檢查的數據庫; --no-check-binlog-format #不檢查複製的binlog模式,要是binlog模式是ROW,則會報錯; --replicate-check-only #只顯示不同步的信息; --replicate=test.checksums #把checksum的信息寫入到指定表中,建議直接寫到被檢查的數據庫當中; --databases=test #指定需要被檢查的數據庫,多個則用逗號隔開; --tables=tt #指定需要被檢查的表,多個用逗號隔開; --socket= #可指定Master的socket; h=127.0.0.1 #Master的地址; u=root #用戶名(Master和Slave可共用); p=123456 #密碼(Master和Slave可共用); P=3306 #Master的端口; |
上面出現了報錯信息:Diffs cannot be detected because no slaves were found. Please read the –recursion-method documentation for information.
提示信息很清楚,因爲找不到從,所以執行失敗。我們需要用參數–recursion-method指定模式解決,關於–recursion-method參數的設置有:
METHOD USES =========== ============================================= processlist SHOW PROCESSLIST hosts SHOW SLAVE HOSTS cluster SHOW STATUS LIKE 'wsrep\_incoming\_addresses' dsn=DSN DSNs from a table none Do not find slaves
1 2 3 4 5 6 7 |
METHOD USES =========== ============================================= processlist SHOW PROCESSLIST hosts SHOW SLAVE HOSTS cluster SHOW STATUS LIKE 'wsrep\_incoming\_addresses' dsn=DSN DSNs from a table none Do not find slaves |
默認是通過show processlist找到slave host的值。還有一種方法是通過show slave hosts;找到slave host的值,前提是從庫配置文件裏面已經配置自己的地址和端口:
$ grep 'report' /etc/my.cnf report_host = 10.0.60.143 report_port = 3307
1 2 3 |
$ grep 'report' /etc/my.cnf report_host = 10.0.60.143 report_port = 3307 |
mysql> show slave hosts; +-----------+-------------+------+-----------+--------------------------------------+ | Server_id | Host | Port | Master_id | Slave_UUID | +-----------+-------------+------+-----------+--------------------------------------+ | 20 | 10.0.60.143 | 3307 | 10 | 47af3f9d-af94-11e6-8b5b-001dd8b71e2b | +-----------+-------------+------+-----------+--------------------------------------+ 1 row in set (0.00 sec)
1 2 3 4 5 6 7 |
mysql> show slave hosts; +-----------+-------------+------+-----------+--------------------------------------+ | Server_id | Host | Port | Master_id | Slave_UUID | +-----------+-------------+------+-----------+--------------------------------------+ | 20 | 10.0.60.143 | 3307 | 10 | 47af3f9d-af94-11e6-8b5b-001dd8b71e2b | +-----------+-------------+------+-----------+--------------------------------------+ 1 row in set (0.00 sec) |
所以找不到從服務器時,在從庫配置文件添加:
report_host=slave_ip
report_port=slave_port
現在我們再來檢測數據一致性,我這裏使用hosts方式找到從庫:
$ pt-table-checksum --nocheck-replication-filters --no-check-binlog-format --replicate=test.checksums --databases=test --tables=tt h=10.0.60.143,u=root,p='123456',P=3306 --recursion-method=hosts TS ERRORS DIFFS ROWS CHUNKS SKIPPED TIME TABLE 11-22T11:58:01 0 1 3 1 0 0.092 test.tt
1 2 3 |
$ pt-table-checksum --nocheck-replication-filters --no-check-binlog-format --replicate=test.checksums --databases=test --tables=tt h=10.0.60.143,u=root,p='123456',P=3306 --recursion-method=hosts TS ERRORS DIFFS ROWS CHUNKS SKIPPED TIME TABLE 11-22T11:58:01 0 1 3 1 0 0.092 test.tt |
TS:完成檢查的時間。
ERRORS:檢查時候發生錯誤和警告的數量。
DIFFS:0表示一致,1表示不一致,當指定–no-replicate-check時,會一直爲0;當指定–replicate-check-only會顯示不同的信息。
ROWS:表的行數。
CHUNKS:被劃分到表中的塊的數目。
SKIPPED:由於錯誤或警告或過大,則跳過塊的數目。
TIME:執行的時間。
TABLE:被檢查的表名。
好了,命令以及常用參數都介紹了,一起解釋下上面執行的效果:
通過DIFFS是1就可以看出主從的表數據不一致。怎麼不一致呢? 通過指定–replicate=test.checksums參數,就說明把檢查信息都寫到了checksums表中。
進入SLAVE相應的庫中查看checksums表的信息:
mysql> select * from test.checksums\G *************************** 1. row *************************** db: test tbl: tt chunk: 1 chunk_time: 0.005582 chunk_index: NULL lower_boundary: NULL upper_boundary: NULL this_crc: 699fed16 this_cnt: 3 master_crc: 699fed16 master_cnt: 3 ts: 2016-11-22 13:25:16 1 row in set (0.00 sec)
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 |
mysql> select * from test.checksums\G *************************** 1. row *************************** db: test tbl: tt chunk: 1 chunk_time: 0.005582 chunk_index: NULL lower_boundary: NULL upper_boundary: NULL this_crc: 699fed16 this_cnt: 3 master_crc: 699fed16 master_cnt: 3 ts: 2016-11-22 13:25:16 1 row in set (0.00 sec) |
通過上面找到了這些不一致的數據表,如何同步數據呢?即如何修復MySQL主從不一致的數據,讓他們保持一致性呢?利用另外一個工具pt-table-sync。
四、pt-table-sync工具使用
使用方法:
$ pt-table-sync [OPTIONS] DSN [DSN]
1 |
$ pt-table-sync [OPTIONS] DSN [DSN] |
pt-table-sync高效的同步MySQL表之間的數據,他可以做單向和雙向同步的表數據。他可以同步單個表,也可以同步整個庫。它不同步表結構、索引、或任何其他模式對象。所以在修復一致性之前需要保證他們表存在。需要注意的是這個命令需要在Slave從庫執行。
接着上面的複製情況,主和從的tt表數據不一致,需要修復。我們連接從庫開始執行pt-table-sync,使用print參數,他會在屏幕顯示修復的SQL語句。然後可以手工確認並執行。
$ pt-table-sync --sync-to-master h=10.0.60.143,u=root,p='123456',P=3307 --print
1 |
$ pt-table-sync --sync-to-master h=10.0.60.143,u=root,p='123456',P=3307 --print |
參數的意義:
--replicate= #指定通過pt-table-checksum得到的表,這2個工具差不多都會一直用。 --databases= #指定執行同步的數據庫,多個用逗號隔開。 --tables= #指定執行同步的表,多個用逗號隔開。 --sync-to-master #指定一個DSN,即從的IP、端口、用戶、密碼等,他會通過show processlist或show slave status去自動的找主。 h=10.0.60.143 #Slave服務器地址。 u=root #帳號。 p=123456 #密碼。 P=3307 #端口。 --print #打印SQL語句,但不執行命令。 --execute #執行命令。
1 2 3 4 5 6 7 8 9 10 |
--replicate= #指定通過pt-table-checksum得到的表,這2個工具差不多都會一直用。 --databases= #指定執行同步的數據庫,多個用逗號隔開。 --tables= #指定執行同步的表,多個用逗號隔開。 --sync-to-master #指定一個DSN,即從的IP、端口、用戶、密碼等,他會通過show processlist或show slave status去自動的找主。 h=10.0.60.143 #Slave服務器地址。 u=root #帳號。 p=123456 #密碼。 P=3307 #端口。 --print #打印SQL語句,但不執行命令。 --execute #執行命令。 |
也可以通過這個命令自動執行,不過這樣會修改從庫的數據,感覺不是太安全。
$ pt-table-sync --sync-to-master h=10.0.60.143,u=root,p='123456',P=3307 --execute
1 |
$ pt-table-sync --sync-to-master h=10.0.60.143,u=root,p='123456',P=3307 --execute |
此時應該已經修復了從庫的數據,然後檢查主從數據的一致性驗證一下:
$ pt-table-checksum --nocheck-replication-filters --no-check-binlog-format --replicate=test.checksums --databases=test --tables=tt h=10.0.60.143,u=root,p=123456,P=3306 --recursion-method=hosts TS ERRORS DIFFS ROWS CHUNKS SKIPPED TIME TABLE 04-13T16:27:28 0 0 3 1 0 0.097 test.tt
1 2 3 |
$ pt-table-checksum --nocheck-replication-filters --no-check-binlog-format --replicate=test.checksums --databases=test --tables=tt h=10.0.60.143,u=root,p=123456,P=3306 --recursion-method=hosts TS ERRORS DIFFS ROWS CHUNKS SKIPPED TIME TABLE 04-13T16:27:28 0 0 3 1 0 0.097 test.tt |
主庫(3306):
mysql> select * from test.tt; +----+-------+ | id | count | +----+-------+ | 1 | 1 | | 2 | 2 | | 3 | 3 | +----+-------+ 3 rows in set (0.00 sec)
1 2 3 4 5 6 7 8 9 |
mysql> select * from test.tt; +----+-------+ | id | count | +----+-------+ | 1 | 1 | | 2 | 2 | | 3 | 3 | +----+-------+ 3 rows in set (0.00 sec) |
從庫(3307):
mysql> select * from test.tt; +----+-------+ | id | count | +----+-------+ | 1 | 1 | | 2 | 2 | | 3 | 3 | +----+-------+ 3 rows in set (0.00 sec)
1 2 3 4 5 6 7 8 9 |
mysql> select * from test.tt; +----+-------+ | id | count | +----+-------+ | 1 | 1 | | 2 | 2 | | 3 | 3 | +----+-------+ 3 rows in set (0.00 sec) |
OK,數據已經保持一致了。不過建議還是用–print打印出來的好,這樣就可以知道那些數據有問題,可以人爲的干預下。不然直接執行了,出現問題之後更不好處理。總之還是在處理之前做好數據的備份工作。
注意:要是表中沒有唯一索引或則主鍵則會報錯:
Can't make changes on the master because no unique index exists at /usr/local/bin/pt-table-sync line 10591.
1 |
Can't make changes on the master because no unique index exists at /usr/local/bin/pt-table-sync line 10591. |
五、基本工作原理
5.1 pt-table-checksum工作原理
Step1: 在Master主庫執行pt-table-checksum命令。首先對比主庫和的從庫的表結構進行檢查,如果結構不一致則報錯,停止修復。
Step2: 如果一行一行去比較,效率會很低,所以pt-table-checksum會根據表的索引,將表分成一個一個的chunk,每個chunk默認1000行,這個值可以根據服務器性能進行調整。
Step3: 將每個chunk中的每一行的所有列都轉化爲字符串,並用concat_ws()函數將所有列拼接起來的到一個大的”總字符串“,之後我們用BIT_XOR()聚合函數將每個chunk的所有行的”總字符串“進行組合拼接,之後計算出整個chunk的crc32校驗覈(checksum值)。
Step4: 在從庫上執行Step3同樣的操作,計算出從庫表各個chunk的checksum值。
Step5: 將主庫中表的各個chunk塊的checkum值和從庫表的各個chunk塊checksum值都存儲在replicate參數指定的checksum結果表中。(都在從庫中)
Step6: 檢測完畢,我們去檢查從庫的replicate參數指定的checksum結果表就可以了。master_src列和master_cnt列代表主庫,this_src列和this_cnt列代表從庫。
注意:Step3在計算checksum時候,爲了保證一致性,需要在語句中加入for update鎖住具體chunk中的行,會有阻塞產生。如果是MyISAM這類不支持事務的表,則會鎖表。
主要函數:使用concat_ws函數將數據合併爲一行,然後使用crc32函數生成校驗碼,最後將其插入到指定的checksums表中。
mysql> select concat_ws(',',id,count) from tt; +-------------------------+ | concat_ws(',',id,count) | +-------------------------+ | 1,1 | | 2,2 | | 3,3 | +-------------------------+ 3 rows in set (0.00 sec) mysql> select crc32(concat_ws(',',id,count)) from test.tt; +--------------------------------+ | crc32(concat_ws(',',id,count)) | +--------------------------------+ | 2986818849 | | 692638402 | | 1603111011 | +--------------------------------+ 3 rows in set (0.00 sec)
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 |
mysql> select concat_ws(',',id,count) from tt; +-------------------------+ | concat_ws(',',id,count) | +-------------------------+ | 1,1 | | 2,2 | | 3,3 | +-------------------------+ 3 rows in set (0.00 sec)
mysql> select crc32(concat_ws(',',id,count)) from test.tt; +--------------------------------+ | crc32(concat_ws(',',id,count)) | +--------------------------------+ | 2986818849 | | 692638402 | | 1603111011 | +--------------------------------+ 3 rows in set (0.00 sec) |
5.2 pt-table-sync工作原理
Step1: 首先,定位到每個database中的每個表中的每一個chunk,發現有不一致的chunk後,查詢主庫的show master status。之後在從庫執行select master_pos_wait(‘主庫當前binglog日誌名’,’主庫當前binlog日誌位置’);該步驟的目的主要是阻塞從庫,使其達到主庫的二進制日誌位置,從而主從同步。
Step2: 過濾不一致chunk中的每一行,依然採用checksum來比較其是否不一致。檢測到不一致後進行記錄。
Step3: 將不一致的行,在主庫轉化爲replace into語句(此語句會將主鍵不存在數據插入到表中,主鍵重複的進行update),通過binlog傳播到從庫,並在從庫執行。
Step4: 如此往復,將所有的數據庫、表和每個chunk的每一行進行修復。
另外,有一些注意事項:
1)pt-table-checksum工具檢查的表可以沒有主鍵或者唯一索引。
2)pt-table-sync工具修復表的時候,表必須有主鍵或者唯一索引。
3)兩個工具在運行的過程中,都會產生對於chunk行塊的寫鎖和一定的負載。所以大家儘量採用腳本的方式在業務低峯期進行。(MyISAM引擎需要鎖住全表)
<參考>
https://yq.aliyun.com/articles/280417?spm=a2c4e.11153959.0.0.37da147cWHTxoH
https://www.percona.com/doc/percona-toolkit/LATEST/pt-table-checksum.html
https://www.percona.com/doc/percona-toolkit/LATEST/pt-table-sync.html