1. 程式人生 > >使用percona-toolkit工具校驗和修復MySQL數據庫主從不一致問題

使用percona-toolkit工具校驗和修復MySQL數據庫主從不一致問題

編譯 led 127.0.0.1 iop created auto ted 形式 鎖表

使用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-checksumPercona-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數據庫主從不一致問題