使用pt-table-checksum&pt-table-sync檢查和修復主從數據一致性

一、背景

公司數據中心從託管機房遷移到阿里雲,需要對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

發表評論
所有評論
還沒有人評論,想成為第一個評論的人麼? 請在上方評論欄輸入並且點擊發布.
相關文章