1. 程式人生 > 其它 >Percona-Toolkit 之 pt-table-checksum 總結--轉發

Percona-Toolkit 之 pt-table-checksum 總結--轉發

目錄


正文

pt-table-checksum - Verify MySQL replication integrity.

pt-table-checksum performs an online replication consistency check by executing checksum queries on the master, which produces different results on replicas that are inconsistent with the master.

pt-table-checksum是Percona-Toolkit工具集中的一個元件,是一個檢查主從複製資料庫資料一致性的工具,通過在主庫上執行檢查語句可以線上檢查MySQL主從資料庫資料的一致性。

pt-table-checksum通過指定選項連線指定伺服器例項,可以通過指定條件過濾到庫級別和表級別。每次檢查一張表,所以並不需要大量的記憶體和多餘的操作,即使是非常大的表也可以良好執行。根據官方文件的描述,之所以在檢查大表上也能很好執行,主要是因為pt-table-checksum將表劃分多個chunk,根據checksum query執行時間來調整chunk的大小,這樣就減少pt-table-checksum的執行對主從複製延遲和主機負載的影響。為了保證對資料庫主機執行儘可能少的影響,pt-table-checksum會自動探測從庫並進行連線,如果連線失敗,可以通過選項--recursion-method指定從庫連線方式。

本文是關於之前有關pt-table-checksum工具使用的學習筆記進行重新整理,使用最新版本的工具同時也進行原理知識的梳理。

關於獲取和安裝Percona-Toolkit工具集可以參考我另一篇博文:Percona-Toolkit 之 pt-online-schema-change 總結中的安裝部分。

回到頂部

基本說明

pt-table-checksum [OPTIONS] DSN

回到頂部

常用選項(OPTIONS)

--ask-pass
命令列提示密碼輸入,保護密碼安全,前提需安裝模組perl-TermReadKey。

--[no]check-binlog-format
預設值:yes
指定檢查所有伺服器上的binlog_format系統引數是否相同。

--check-interval
預設值:1s 指定因為選項'--max-lag'檢查之間休眠時間。 --[no]check-replication-filters 預設值:yes 指定檢測主從複製是否有設定複製過濾器。預設如果有設定複製過濾器,則工具不進行檢查校驗操作。 --check-slave-lag 指定主從複製延遲大於選項'--max-lag'指定的值之後暫停檢查校驗操作。預設情況下,工具會檢查所有的從庫,但該選項只作用於指定的從庫(通過DSN連線方式)。 --chunk-index 指定使用哪個索引對錶進行chunk分塊操作。預設情況下會選擇最優的索引,工具會在SQL語句中新增FORCE INDEX子句。 --chunk-index-columns 指定使用選項'--chunk-index'的索引使用最左字首幾個索引欄位,只適用於複合索引。 --chunk-size 預設值:1000 指定表分塊的chunk大小,每個chunk需要校驗的錶行數,允許的字尾單位為k、M、G。 當指定了這個選項會覆蓋工具預設動態調整chunk塊大小以便在選項'--chunk-time'指定時間內完成行的校驗。所以在大多數情況下不建議使用該選項,應該使用選項'--chunk-time'來調整。 --chunk-size-limit 預設值:2.0 指定chunk的行數最多可以超過選項'--chunk-size'指定的行數的多少倍。最小值是1,表示chunk的行數不能超過選項'--chunk-size'指定的值。由於行數是通過explain估算的,所以不建議指定為1。當引數值為0時,則不會檢查是否超過指定的行數。 --chunk-time 預設值:0.5 動態調整每個chunk的大小使相應的錶行數都在指定的時間內完成校驗操作。 如果該選項值設定為0,則不會動態調整chunk的大小,就有可能造成每次校驗操作的時間不同,但每個chunk大小還是一致的。 --columns,-c 指定只需要校驗的欄位,如有多個則用','(逗號)隔開。該選項一般只針對檢驗一張表時有效,除非有多張表具有相同的欄位。 --[no]create-replicate-table 預設值:yes 建立選項'--replicate'指定的資料庫和表。表結構與選項'--replicate'指定的結構相同。 --replicate 預設值:percona.checksums 指定儲存校驗結果的表。建立表的結構如下: ' 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 DEFAULT CURRENT_TIMESTAMP ON UPDATE CURRENT_TIMESTAMP, PRIMARY KEY (db, tbl, chunk), INDEX ts_db_tbl (ts, db, tbl) ) ENGINE=InnoDB DEFAULT CHARSET=utf8; ' 因為選項'--[no]create-replicate-table'預設值為true,所以預設情況下如果資料庫和表之前不存在則會自動建立percona庫和checksums表。除非另有選項指定。選項'--replicate'指定的表不會進行校驗操作,指定的表預設加入選項'--ignore-tables'。 --[no]replicate-check 預設值:yes 指定在校驗完每張表後檢查主從當前表是否出現不一致。工具通過連線到從庫執行簡單的SELECT語句查詢校驗結果與主庫的校驗結果進行差異對比,結果顯示在輸出中的DIFF欄位中。 --replicate-check-only 指定僅僅執行檢查主從資料是否一致而不進執行真正的校驗操作(主要通過查詢之前保留的校驗結果)。該選項只適用於同時指定選項'--no-replicate-check'--replicate-check-retries 預設值:1 指定當校驗出主從資料不一致重試校驗的次數。 --replicate-database 指定工具在執行校驗操作時在哪個資料庫下進行,相當於執行了語句USE [DB_NAME]。 --resume 指定從最後完成校驗的chunk開始恢復校驗。適用於還未完成所有表的校驗就工具就中斷的情況。 --retries 預設值:2 指定當出現非嚴重性錯誤時重複校驗一個塊的次數。非嚴重性錯誤指的是如鎖等待超時或長查詢被kill的情況。 --run-time 指定校驗操作執行的時間。預設情況需要校驗完所有的表資料後停止,可以指定時間單位為:s(秒)、m(分鐘)、h(小時)、d(天)。 --skip-check-slave-lag DSN型別,可重複使用 指定DSN連線從庫時跳過主從延遲檢查,可以指定多個從庫檢查。 --set-vars 預設: wait_timeout=10000 innodb_lock_wait_timeout=1 lock_wait_timeout=60 執行檢查時指定引數值,如有多個用','(逗號)分隔。如'--set-vars=wait_timeout=5000'--[no]empty-replicate-table 預設值:yes 指定進行當前校驗之前刪除之前每張表的校驗記錄。該選項並不是對儲存校驗結果的表進行truncate,而是在校驗每張表之前刪除當前表之前的校驗結果,因此當校驗操作過早停止,則有可能還有表沒有校驗資料,如果是從之前校驗操作恢復,也不會清空儲存校驗結果的表。如果想清空儲存校驗結果的表,則在校驗操作進行之前手動對錶執行truncate操作。 --databases,-d 指定只需要校驗的資料庫,如有多個則用','(逗號)隔開。 --engines,-e 指定只需要校驗的指定儲存引擎型別的表。 --explain 指定顯示校驗查詢語句,但不執行真正的校驗操作。該選項會禁用選項'--[no]empty-replicate-table',如果指定兩次,則工具實際使用的是迭代的chunk演算法,打印出每個塊的上邊界和下邊界值,但不執行真正的校驗。 --fail-on-stopped-replication 指定當主從複製停止時,校驗中止操作並提示錯誤而不是等待主從複製恢復之後再進行。 --function 預設值:CRC32 指定校驗操作使用的雜湊函式。可選函式有SHA1MD5等。 --ignore-columns 指定需要忽略校驗的欄位,如有多個則用','(逗號)隔開。 --ignore-databases 指定需要忽略校驗的資料庫,如有多個則用','(逗號)隔開。 --ignore-databases-regex 指定採用正則表示式匹配忽略校驗的資料庫。 --ignore-engines 預設值:FEDERATED,MRG_MyISAM 指定需要忽略校驗的儲存引擎型別的表,如有多個則用','(逗號)隔開。 --ignore-tables 指定需要忽略校驗的表,如有多個則用','(逗號)隔開。表名稱可以使用資料庫名加以限定。 --ignore-tables-regex 指定採用正則表示式匹配忽略校驗的表。 --max-lag 預設值:1s 指定允許主從複製延遲時長的最大值,單位秒。如果在每次校驗查詢之後主從延遲超過指定的值,則校驗操作將暫停執行,暫停休眠時間為選項'--check-interval'指定的值。待休眠時間結束之後再次檢查主從延遲時長,檢查方法是通過從庫查詢的'Seconds_Behind_Master'值來確定。如果主從複製延遲一直大於該引數指定值或者從庫停止複製,則操作將一直等待直到從庫重新啟動並且延遲小於該引數指定值。 --max-load 陣列型別,預設值:Threads_running = 25 在校驗要詢完每個chunk資料之後,執行SHOW GLOBAL STATUS檢查所指定變數值高於該引數指定變數的閾值時將暫停校驗操作。如果有多個變數閾值,可以用','(逗號)進行分隔,引數指定形式可以為變數名=MAX_VALUE或變數名:MAX_VALUE。 如果只是指定變數名,沒有為其指定閾值,則檢查當前值並增加20%作為閾值。如: --max-load=Threads_running:沒有指定具體值,以當前查詢值增加20%作為閾值,如當前為100,閾值為120; --max-load=Threads_running:10:以當前指定值為閾值。 --host,-h 指定連線的資料庫IP地址。 --port,-P 指定連線的資料庫Port埠。 --user,-u 指定連線的資料庫使用者。 --password,-p 指定連線的資料庫使用者密碼。 --database,-d 指定連線的資料庫。 --socket,-S 指定使用SOCKET檔案連線。 --progress 列印工具執行過程的進度提示到STDERR。選項值有兩部分組成,用逗號進行分隔,第一部分為百分比,時間和迭代。第二部分為根據第一部分資料更新頻率,也分為百分比,時間和迭代。 --quiet,-q 不列印工具執行過程的資訊到STDOUT(禁用'--progress')。但錯誤和警告還是列印到STDERR。 --recurse 指定搜尋從庫的層級,預設無限級。 --recursion-method 預設值:processlisthosts 指定獲取從庫的方式。pt-table-checksum在執行校驗操作時會執行多次REPLICA CHECKS操作。 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 ========================================================== processlist:通過SHOW PROCESSLIST方式找到slave,為預設方式,當SHOW SLAVE HOSTS不可用時。一旦例項執行在非3306埠上時,hosts方式就會變為預設方式; hosts:通過SHOW SLAVE HOSTS方式找到slavehosts方式要求從庫配置'--report_host''--report_port'這兩個引數; cluster:基於叢集版本Galera 23.7.3及更新版本; dsn:通過讀取表中從庫的DSN資訊進行連線。 --tables,-t 指定只需要校驗的表,如有多個則用','(逗號)隔開。表名稱可以使用資料庫名加以限定。 --tables-regex 指定採用正則表示式匹配校驗的表。 --where 指定通過where條件確定表中需要校驗的資料。 --truncate-replicate-table 指定在執行校驗操作之前對儲存校驗結果的表執行truncate操作。注意與選項'--[no]empty-replicate-table'的區分。 --version 顯示工具的版本並退出。 --[no]version-check 預設值:yes 檢查Percona Toolkit、MySQL和其他程式的最新版本。
回到頂部

DSN選項(DSN)

可以使用DSN方式來連線資料庫,DSN選項為key=value方式,在等號的兩側不能有空格出現,並且區分大小寫,多個選項之前以','(逗號)隔開,主要選項如下:

  • A
    指定字符集
  • D
    指定DSN表所在資料庫
  • t
    指定DSN表
  • h
    指定要連線的HOST
  • P
    指定要連線的PORT
  • S
    指定連線所使用的SOCKET檔案(Unix systems)
  • u
    指定連線的使用者名稱
  • p
    指定連線的使用者名稱密碼

示例:
h=192.168.58.3,P=3306,D=employees,t=employees

回到頂部

從庫檢查(REPLICA CHECKS)

預設情況,pt-table-checksum工具會對所有連線到的從庫進行檢查,從庫連線策略由選項--recursion-method--recurse控制,檢查專案如下:

  • --[no]check-replication-filters

工具檢查每個從庫上是否有存在主從複製過濾器,如果發現有複製過濾器,則校驗操作會中止,工具會退出。

  • --replicate指定的表

工具檢查每個從庫上是否都存在選項--replicate指定儲存校驗結果的表。如果有從庫沒有這張表時,校驗操作有可能導致主從複製中斷。這項檢查無法被關閉,如果從庫不存在表,則工具會一直等待直到從庫存在該表。

  • 單個chunk大小

工具檢查主庫上表的是否可以在單個chunk範圍內進行校驗。具體說明可以參考官方說明:REPLICA CHECKS

  • 主從複製延遲

工具在每次校驗完一個chunk之後都會檢查每個從庫是否有延遲,或者通過選項--check-slave-lag指定需要檢查的從庫。

  • 校驗塊

工具在主庫校驗完每張表之後都會等待每個從庫最後一個校驗塊的完成,再執行選項--[no]replicate-check指定的操作。

回到頂部

使用限制

  1. pt-table-checksum工具要求主從複製是基於STATEMENT格式進行的複製,並且會在主庫執行binlog_format=STATEMENT語句。由於MySQL的限制,這一設定並不會傳遞到從庫進行。因此當從庫的binlog格式是ROW時,無法直接對從庫的從庫進行校驗操作。工具在進行校驗操作時都會檢查所有從庫的binlog_format,可以使用選項--[no]check-binlog-format進行控制;
  2. 工具假設主從資料庫中SCHEMA和表結構都是一致的。如果從庫上不存在主庫的SCHEMA,或者是從庫上表結構與主庫不一致,則有可能導致主從複製中斷。
回到頂部

用法示例

回到頂部

環境與資料準備

MySQL:5.7.24
MASTER:192.168.58.3:3306
SLAVE:192.168.58.5:3306

本文基於MySQL官方示例資料庫employeeExample Databases進行測試。

建立測試表employees_ptchksum:

-- 表employees_ptchksum資訊
mysql [email protected]:employees> create table employees_ptchksum as select * from employees;
Query OK, 300024 rows affected
Time: 2.688s

mysql [email protected]:employees> show create table employees_ptchksum\G;
***************************[ 1. row ]***************************
Table        | employees_ptchksum
Create Table | CREATE TABLE `employees_ptchksum` (
  `emp_no` int(11) NOT NULL,
  `birth_date` date NOT NULL,
  `first_name` varchar(14) NOT NULL,
  `last_name` varchar(16) NOT NULL,
  `gender` enum('M','F') NOT NULL,
  `hire_date` date NOT NULL
) ENGINE=InnoDB DEFAULT CHARSET=utf8
1 row in set
Time: 0.007s

-- 從庫更新部分資料,使得主從出現數據不一致
mysql admin@192.168.58.5:employees> set sql_log_bin = 0;
Query OK, 0 rows affected
Time: 0.002s

mysql [email protected]:employees> update employees_ptchksum set first_name = 'Georgi_ptchk' where first_name = 'Georgi';
Query OK, 252 rows affected
Time: 0.235s

mysql [email protected]:employees> set sql_log_bin = 1;
Query OK, 0 rows affected
Time: 0.002s
回到頂部

執行一致性校驗

在主庫上執行:

# pt-table-checksum h=192.168.58.3,P=3306,u=admin --ask-pass --tables=employees_ptchksum

執行以上命令會報如下錯誤:

Replica dbabd 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.

根據錯誤資訊需指定選項--no-check-binlog-format,加上選項再次執行:

# pt-table-checksum h=192.168.58.3,P=3306,u=admin --ask-pass --tables=employees_ptchksum --no-check-binlog-format

執行以上命令報如下錯誤:

04-02T15:45:40 Cannot checksum table employees.employees_ptchksum: There is no good index and the table is oversized. at /usr/bin/pt-table-checksum line 6726.

根據錯誤資訊提示表employees_ptchksum沒有合適的索引進行chunk操作,為表新增主鍵後再次執行校驗:

-- 新增主鍵
mysql [email protected]:employees> alter table employees_ptchksum add primary key(emp_no);
Query OK, 0 rows affected
Time: 2.271s

-- 再次執行校驗
# pt-table-checksum h=192.168.58.3,P=3306,u=admin --ask-pass --tables=employees.employees_ptchksum --no-check-binlog-format
Enter MySQL password:

Checking if all tables can be checksummed ...
Starting checksum ...
            TS ERRORS  DIFFS     ROWS  DIFF_ROWS  CHUNKS SKIPPED    TIME TABLE
04-02T15:56:21      0      3   300024          0       5       0   0.845 employees.employees_ptchksum
回到頂部

輸出欄位解析

  • TS:校驗完成的時間戳(沒有年份顯示);
  • ERRORS: 校驗報錯的數量;
  • DIFFS:主從之間chunk不同的數量,如果指定--no-replicate-check,則該值總是為0,如果指定--replicate-check-only,則只有校驗結果不同的表會顯示;
  • ROWS:選擇表校驗的行數;
  • CHUNKS:表被分成的chunk數;
  • SKIPPED:跳過的chunk數,主要有以下原因:
    • MySQL not using the --chunk-index
    • MySQL not using the full chunk index (--[no]check-plan)
    • Chunk size is greater than --chunk-size * --chunk-size-limit
    • Lock wait timeout exceeded (--retries)
    • Checksum query killed (--retries)
  • TIME:校驗執行消耗時間(單位:秒)
  • TABLE:校驗的表名

使用選項--replicate-check-only輸出欄位如下:

Checking if all tables can be checksummed ...
Starting checksum ...
Differences on dbabd
TABLE                        CHUNK CNT_DIFF CRC_DIFF CHUNK_INDEX LOWER_BOUNDARY UPPER_BOUNDARY
employees.employees_ptchksum 1     0        1        PRIMARY     10001          11000
employees.employees_ptchksum 2     0        1        PRIMARY     11001          35858
employees.employees_ptchksum 3     0        1        PRIMARY     35859          491954
employees.employees_ptchksum 4     0        1        PRIMARY     491955         499999
  • TABLE:與主庫資料不一致的表;
  • CHUNK:與主庫相應表不同的chunk;
  • CNT_DIFF:從庫上chunk行數減去主庫上chunk行數值;
  • CRC_DIFF:如果從庫上對應的chunk與主庫上不同,則為1,否則為0;
  • CHUNK_INDEX:表使用哪個索引用來進行chunk;
  • LOWER_BOUNDARY:chunk下邊界對應的索引值;
  • UPPER_BOUNDARY:chunk上邊界對應的索引值。
回到頂部

獲取校驗結果

在從庫上執行:

mysql [email protected]:employees> SELECT db, tbl, SUM(this_cnt) AS total_rows, COUNT(*) AS chunks
                                    FROM percona.checksums
                                    WHERE (
                                     master_cnt <> this_cnt
                                     OR master_crc <> this_crc
                                     OR ISNULL(master_crc) <> ISNULL(this_crc))
                                    GROUP BY db, tbl;
+-----------+--------------------+------------+--------+
| db        | tbl                | total_rows | chunks |
+-----------+--------------------+------------+--------+
| employees | employees_ptchksum | 300024     | 4      |
+-----------+--------------------+------------+--------+
1 row in set
Time: 0.021s
回到頂部

工具其他選項

  • 主庫為標準埠(3306),從庫為非標準埠(非3306)

修改從庫埠為3307:

mysql [email protected]:employees> show global variables like 'port';
+---------------+-------+
| Variable_name | Value |
+---------------+-------+
| port          | 3307  |
+---------------+-------+
1 row in set
Time: 0.027s

執行一致性校驗,會報出如下錯誤:

# pt-table-checksum h=192.168.58.3,P=3306,u=admin --ask-pass --databases=employees --tables=employees_ptchksum --no-check-binlog-format
Enter MySQL password:

Checking if all tables can be checksummed ...
Starting checksum ...
Cannot connect to P=3306,h=192.168.58.5,p=...,u=admin
Diffs cannot be detected because no slaves were found.  Please read the --recursion-method documentation for information.

根據報錯資訊看出,工具找不到從庫,因為現在從庫執行在3307埠上,而工具預設連線的是3306埠,所以需要指定選項--recursion-method,關於選項的使用參考前面說明。

因為主庫執行在標準埠,而從庫在非標準埠,所以選項--recursion-method值只能為hostsdsn,以下分兩種情況討論:

-- 指定--recursion-method=hosts,從庫必須配置引數report_host和report_port
mysql [email protected]:employees> show global variables like '%report%';
+-----------------+--------------+
| Variable_name   | Value        |
+-----------------+--------------+
| report_host     | 192.168.58.5 |
| report_password |              |
| report_port     | 3307         |
| report_user     |              |
+-----------------+--------------+
4 rows in set
Time: 0.035s

-- 執行一致性校驗
# pt-table-checksum h=192.168.58.3,P=3306,u=admin --ask-pass --databases=employees --tables=employees_ptchksum --no-check-binlog-format --recursion-method hosts

------------------------------------------------------------------

-- 指定--recursion-method=dsn,主庫必須先建立dsns表,表結構如下
mysql admin@192.168.58.3:percona> show create table dsns;
+-------+-------------------------------------------------------+
| Table | Create Table                                          |
+-------+-------------------------------------------------------+
| dsns  | CREATE TABLE `dsns` (                                 |
|       |   `id` int(11) NOT NULL AUTO_INCREMENT,               |
|       |   `parent_id` int(11) DEFAULT NULL,                   |
|       |   `dsn` varchar(255) NOT NULL,                        |
|       |   PRIMARY KEY (`id`)                                  |
|       | ) ENGINE=InnoDB AUTO_INCREMENT=2 DEFAULT CHARSET=utf8 |
+-------+-------------------------------------------------------+
1 row in set
Time: 0.037s

-- 表中插入從庫dsn資訊
mysql admin@192.168.58.3:percona> select * from dsns;
+----+-----------+-----------------------+
| id | parent_id | dsn                   |
+----+-----------+-----------------------+
| 1  | 1         | h=192.168.58.5,P=3307 |
+----+-----------+-----------------------+
1 row in set
Time: 0.022s

-- 執行一致性校驗
# pt-table-checksum h=192.168.58.3,P=3306,u=admin --ask-pass --databases=employees --tables=employees_ptchksum --no-check-binlog-format --recursion-method dsn=D=percona,t=dsns
  • 主庫和從庫都為非標準埠(非3306)

修改主庫埠為3307:

mysql [email protected]:employees> show global variables like 'port';
+---------------+-------+
| Variable_name | Value |
+---------------+-------+
| port          | 3307  |
+---------------+-------+
1 row in set
Time: 0.021s

同樣,如果指定選項--recursion-method hosts,則從庫必須配置引數report_host和report_port,這裡不再贅述。或者選項--recursion-method值為processlistdsn

-- 指定--recursion-method processlist
# pt-table-checksum h=192.168.58.3,P=3307,u=admin --ask-pass --databases=employees --tables=employees_ptchksum --no-check-binlog-format --recursion-method processlist

-- 指定--recursion-method=dsn
# pt-table-checksum h=192.168.58.3,P=3307,u=admin --ask-pass --databases=employees --tables=employees_ptchksum --no-check-binlog-format --recursion-method dsn=D=percona,t=dsns

關於主從埠的總結(選項--recursion-method值的設定):

  1. 對於所有型別的埠設定,dsn方式都可以使用,最為方便,也便於進行管理;
  2. 如果要使用hosts方式,則從庫必須配置引數report_hostreport_port
  3. 主庫如果是標準埠(3306),則只能使用hostsdsn方式;
  4. 主庫如果是非標準埠(非3306),則根據從庫是否配置引數report_hostreport_port來決定:
    有配置可選擇hosts方式;
    沒有配置則只能選擇processlistdsn方式。
  • 執行整個資料庫校驗
# pt-table-checksum h=192.168.58.3,P=3306,u=admin --ask-pass --databases=employees --no-check-binlog-format --recursion-method dsn=D=percona,t=dsns
  • 執行表某個欄位校驗

這裡以employees_ptchksum表的first_name欄位為例。

# pt-table-checksum h=192.168.58.3,P=3306,u=admin --ask-pass --databases=employees --tables=employees_ptchksum --columns=first_name --no-check-binlog-format --recursion-method dsn=D=percona,t=dsns
  • 只打印校驗查詢語句

指定選項--explain

# pt-table-checksum h=192.168.58.3,P=3306,u=admin --ask-pass --databases=employees --tables=employees_ptchksum --no-check-binlog-format --recursion-method dsn=D=percona,t=dsns --explain
Enter MySQL password:

Checking if all tables can be checksummed ...
Starting checksum ...
--
-- employees.employees_ptchksum
--

REPLACE INTO `percona`.`checksums` (db, tbl, chunk, chunk_index, lower_boundary, upper_boundary, this_cnt, this_crc) SELECT ?, ?, ?, ?, ?, ?, COUNT(*) AS cnt, COALESCE(LOWER(CONV(BIT_XOR(CAST(CRC32(CONCAT_WS('#', `emp_no`, `birth_date`, convert(`first_name` using utf8mb4), convert(`last_name` using utf8mb4), convert(`gender` using utf8mb4), `hire_date`)) AS UNSIGNED)), 10, 16)), 0) AS crc FROM `employees`.`employees_ptchksum` FORCE INDEX(`PRIMARY`) WHERE ((`emp_no` >= ?)) AND ((`emp_no` <= ?)) /*checksum chunk*/

REPLACE INTO `percona`.`checksums` (db, tbl, chunk, chunk_index, lower_boundary, upper_boundary, this_cnt, this_crc) SELECT ?, ?, ?, ?, ?, ?, COUNT(*), '0' FROM `employees`.`employees_ptchksum` FORCE INDEX(`PRIMARY`) WHERE ((`emp_no` < ?)) ORDER BY `emp_no` /*past lower chunk*/

REPLACE INTO `percona`.`checksums` (db, tbl, chunk, chunk_index, lower_boundary, upper_boundary, this_cnt, this_crc) SELECT ?, ?, ?, ?, ?, ?, COUNT(*), '0' FROM `employees`.`employees_ptchksum` FORCE INDEX(`PRIMARY`) WHERE ((`emp_no` > ?)) ORDER BY `emp_no` /*past upper chunk*/

SELECT /*!40001 SQL_NO_CACHE */ `emp_no` FROM `employees`.`employees_ptchksum` FORCE INDEX(`PRIMARY`) WHERE ((`emp_no` >= ?)) ORDER BY `emp_no` LIMIT ?, 2 /*next chunk boundary*/
  • 指定校驗分塊(chunk)大小

會覆蓋工具動態調整chunk大小的行為。

# pt-table-checksum h=192.168.58.3,P=3306,u=admin --ask-pass --databases=employees --tables=employees_ptchksum --no-check-binlog-format --recursion-method dsn=D=percona,t=dsns --chunk-size=66666
  • 指定每個分塊(chunk)校驗時間

動態調整chunk大小,使得校驗操作可以在指定的時間內完成。

# pt-table-checksum h=192.168.58.3,P=3306,u=admin --ask-pass --databases=employees --tables=employees_ptchksum --no-check-binlog-format --recursion-method dsn=D=percona,t=dsns --chunk-time=2
回到頂部

工作流程

通過general log來了解pt-table-checksum工具如何進行校驗,以及如何進行校驗語句查詢的,以下以校驗employees_ptchksum表為例。

-- 初始的一些檢查資料庫引數、負載資訊這裡不再細說
51 Connect	admin@dbabd1 on  using TCP/IP
51 Query	SHOW VARIABLES LIKE 'innodb\_lock_wait_timeout'
51 Query	SET SESSION innodb_lock_wait_timeout=1
51 Query	SHOW VARIABLES LIKE 'wait\_timeout'
51 Query	SET SESSION wait_timeout=10000
51 Query	SELECT @@SQL_MODE
51 Query	SET @@SQL_QUOTE_SHOW_CREATE = 1/*!40101, @@SQL_MODE='NO_AUTO_VALUE_ON_ZERO,ONLY_FULL_GROUP_BY,STRICT_TRANS_TABLES,NO_ZERO_IN_DATE,NO_ZERO_DATE,ERROR_FOR_DIVISION_BY_ZERO,NO_AUTO_CREATE_USER,NO_ENGINE_SUBSTITUTION'*/
51 Query	SELECT @@server_id /*!50038 , @@hostname*/
51 Query	SELECT @@SQL_MODE
51 Query	SET SQL_MODE=',NO_AUTO_VALUE_ON_ZERO,STRICT_TRANS_TABLES,NO_ZERO_IN_DATE,NO_ZERO_DATE,ERROR_FOR_DIVISION_BY_ZERO,NO_AUTO_CREATE_USER,NO_ENGINE_SUBSTITUTION'
51 Query	SHOW VARIABLES LIKE 'version%'
51 Query	SHOW ENGINES
51 Query	SHOW VARIABLES LIKE 'innodb_version'

-- 設定會話級binlog row格式為STATEMENT
51 Query	SELECT @@binlog_format
51 Query	/*!50108 SET @@binlog_format := 'STATEMENT'*/

-- 設定會話級隔離級別為RR(REPEATABLE READ)
51 Query	SET SESSION TRANSACTION ISOLATION LEVEL REPEATABLE READ
51 Query	SHOW /*!40103 GLOBAL*/ VARIABLES
51 Query	SELECT VERSION()
51 Query	SHOW ENGINES
51 Query	SHOW VARIABLES LIKE 'wsrep_on'
52 Connect	admin@dbabd1 on percona using TCP/IP
52 Query	SHOW VARIABLES LIKE 'innodb\_lock_wait_timeout'
52 Query	SET SESSION innodb_lock_wait_timeout=1
52 Query	SHOW VARIABLES LIKE 'wait\_timeout'
52 Query	SET SESSION wait_timeout=10000
52 Query	SELECT @@SQL_MODE
52 Query	SET @@SQL_QUOTE_SHOW_CREATE = 1/*!40101, @@SQL_MODE='NO_AUTO_VALUE_ON_ZERO,ONLY_FULL_GROUP_BY,STRICT_TRANS_TABLES,NO_ZERO_IN_DATE,NO_ZERO_DATE,ERROR_FOR_DIVISION_BY_ZERO,NO_AUTO_CREATE_USER,NO_ENGINE_SUBSTITUTION'*/
52 Query	SELECT @@server_id /*!50038 , @@hostname*/
52 Query	SHOW VARIABLES LIKE 'wsrep_on'
52 Query	SELECT dsn FROM `percona`.`dsns` ORDER BY id
52 Quit	
51 Query	SHOW VARIABLES LIKE 'wsrep_on'
51 Query	SELECT @@SERVER_ID
51 Query	SHOW VARIABLES LIKE 'wsrep_on'
51 Query	SELECT @@SERVER_ID

-- 建立儲存校驗結果表percona.checksums
51 Query	SHOW DATABASES LIKE 'percona'
51 Query	CREATE DATABASE IF NOT EXISTS `percona` /* pt-table-checksum */
51 Query	USE `percona`
51 Query	SHOW TABLES FROM `percona` LIKE 'checksums'
51 Query	CREATE TABLE IF NOT EXISTS `percona`.`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

-- 檢查資料庫伺服器執行狀態
51 Query	SHOW GLOBAL STATUS LIKE 'Threads_running'
51 Query	SELECT CONCAT(@@hostname, @@port)
51 Query	SELECT CRC32('test-string')
51 Query	SELECT CRC32('a')
51 Query	SELECT CRC32('a')
51 Query	SHOW VARIABLES LIKE 'wsrep_on'
51 Query	SHOW DATABASES
51 Query	SHOW /*!50002 FULL*/ TABLES FROM `employees`
51 Query	/*!40101 SET @OLD_SQL_MODE := @@SQL_MODE, @@SQL_MODE := '', @OLD_QUOTE := @@SQL_QUOTE_SHOW_CREATE, @@SQL_QUOTE_SHOW_CREATE := 1 */

-- 開始對錶employees_ptchksum進行分析
51 Query	USE `employees`
51 Query	SHOW CREATE TABLE `employees`.`employees_ptchksum`
51 Query	/*!40101 SET @@SQL_MODE := @OLD_SQL_MODE, @@SQL_QUOTE_SHOW_CREATE := @OLD_QUOTE */

-- 獲取表資訊,通過主鍵或唯一索引,獲取校驗第一個chunk下邊界起點,這裡為emp_no = 10001
51 Query	EXPLAIN SELECT * FROM `employees`.`employees_ptchksum` WHERE 1=1
51 Query	SELECT /*!40001 SQL_NO_CACHE */ `emp_no` FROM `employees`.`employees_ptchksum` FORCE INDEX(`PRIMARY`) ORDER BY `emp_no` LIMIT 1 /*first lower boundary*/

-- 通過索引獲取校驗錶行數
51 Query	SELECT /*!40001 SQL_NO_CACHE */ `emp_no` FROM `employees`.`employees_ptchksum` FORCE INDEX (`PRIMARY`) WHERE `emp_no` IS NOT NULL ORDER BY `emp_no` LIMIT 1 /*key_len*/
51 Query	EXPLAIN SELECT /*!40001 SQL_NO_CACHE */ * FROM `employees`.`employees_ptchksum` FORCE INDEX (`PRIMARY`) WHERE `emp_no` >= '10001' /*key_len*/

-- 清除表percona.checksums中有關employees_ptchksum表的校驗結果資訊
51 Query	USE `percona`
51 Query	DELETE FROM `percona`.`checksums` WHERE db = 'employees' AND tbl = 'employees_ptchksum'

-- 確定每個chunk包含的行數,首個塊預設為1000行,可以根據系統狀態調整,調整每個塊校驗完成預設時間為0.5s
51 Query	USE `employees`
51 Query	EXPLAIN SELECT /*!40001 SQL_NO_CACHE */ `emp_no` FROM `employees`.`employees_ptchksum` FORCE INDEX(`PRIMARY`) WHERE ((`emp_no` >= '10001')) ORDER BY `emp_no` LIMIT 999, 2 /*next chunk boundary*/
51 Query	SELECT /*!40001 SQL_NO_CACHE */ `emp_no` FROM `employees`.`employees_ptchksum` FORCE INDEX(`PRIMARY`) WHERE ((`emp_no` >= '10001')) ORDER BY `emp_no` LIMIT 999, 2 /*next chunk boundary*/

-- 確定本次chunk校驗查詢的執行計劃,並通過replace into方式寫入校驗結果表
51 Query	EXPLAIN SELECT COUNT(*) AS cnt, COALESCE(LOWER(CONV(BIT_XOR(CAST(CRC32(CONCAT_WS('#', `emp_no`, `birth_date`, convert(`first_name` using utf8mb4), convert(`last_name` using utf8mb4), convert(`gender` using utf8mb4), `hire_date`)) AS UNSIGNED)), 10, 16)), 0) AS crc FROM `employees`.`employees_ptchksum` FORCE INDEX(`PRIMARY`) WHERE ((`emp_no` >= '10001')) AND ((`emp_no` <= '11000')) /*explain checksum chunk*/
51 Query	REPLACE INTO `percona`.`checksums` (db, tbl, chunk, chunk_index, lower_boundary, upper_boundary, this_cnt, this_crc) SELECT 'employees', 'employees_ptchksum', '1', 'PRIMARY', '10001', '11000', COUNT(*) AS cnt, COALESCE(LOWER(CONV(BIT_XOR(CAST(CRC32(CONCAT_WS('#', `emp_no`, `birth_date`, convert(`first_name` using utf8mb4), convert(`last_name` using utf8mb4), convert(`gender` using utf8mb4), `hire_date`)) AS UNSIGNED)), 10, 16)), 0) AS crc FROM `employees`.`employees_ptchksum` FORCE INDEX(`PRIMARY`) WHERE ((`emp_no` >= '10001')) AND ((`emp_no` <= '11000')) /*checksum chunk*/

-- 再次檢視校驗chunk的行數和校驗結果,並更新校驗結果表
51 Query	SHOW WARNINGS
51 Query	SELECT this_crc, this_cnt FROM `percona`.`checksums` WHERE db = 'employees' AND tbl = 'employees_ptchksum' AND chunk = '1'
51 Query	UPDATE `percona`.`checksums` SET chunk_time = '0.007819', master_crc = '4f6eb3dc', master_cnt = '1000' WHERE db = 'employees' AND tbl = 'employees_ptchksum' AND chunk = '1'

-- 檢視資料庫伺服器狀態,並進行下一個chunk的檢查校驗
51 Query	SHOW GLOBAL STATUS LIKE 'Threads_running'
51 Query	EXPLAIN SELECT /*!40001 SQL_NO_CACHE */ `emp_no` FROM `employees`.`employees_ptchksum` FORCE INDEX(`PRIMARY`) WHERE ((`emp_no` >= '11001')) ORDER BY `emp_no` LIMIT 63946, 2 /*next chunk boundary*/
51 Query	SELECT /*!40001 SQL_NO_CACHE */ `emp_no` FROM `employees`.`employees_ptchksum` FORCE INDEX(`PRIMARY`) WHERE ((`emp_no` >= '11001')) ORDER BY `emp_no` LIMIT 63946, 2 /*next chunk boundary*/
51 Query	EXPLAIN SELECT COUNT(*) AS cnt, COALESCE(LOWER(CONV(BIT_XOR(CAST(CRC32(CONCAT_WS('#', `emp_no`, `birth_date`, convert(`first_name` using utf8mb4), convert(`last_name` using utf8mb4), convert(`gender` using utf8mb4), `hire_date`)) AS UNSIGNED)), 10, 16)), 0) AS crc FROM `employees`.`employees_ptchksum` FORCE INDEX(`PRIMARY`) WHERE ((`emp_no` >= '11001')) AND ((`emp_no` <= '74947')) /*explain checksum chunk*/
51 Query	REPLACE INTO `percona`.`checksums` (db, tbl, chunk, chunk_index, lower_boundary, upper_boundary, this_cnt, this_crc) SELECT 'employees', 'employees_ptchksum', '2', 'PRIMARY', '11001', '74947', COUNT(*) AS cnt, COALESCE(LOWER(CONV(BIT_XOR(CAST(CRC32(CONCAT_WS('#', `emp_no`, `birth_date`, convert(`first_name` using utf8mb4), convert(`last_name` using utf8mb4), convert(`gender` using utf8mb4), `hire_date`)) AS UNSIGNED)), 10, 16)), 0) AS crc FROM `employees`.`employees_ptchksum` FORCE INDEX(`PRIMARY`) WHERE ((`emp_no` >= '11001')) AND ((`emp_no` <= '74947')) /*checksum chunk*/
51 Query	SHOW WARNINGS
51 Query	SELECT this_crc, this_cnt FROM `percona`.`checksums` WHERE db = 'employees' AND tbl = 'employees_ptchksum' AND chunk = '2'
51 Query	UPDATE `percona`.`checksums` SET chunk_time = '0.148209', master_crc = '48c0faee', master_cnt = '63947' WHERE db = 'employees' AND tbl = 'employees_ptchksum' AND chunk = '2'

……省略……

51 Query	SHOW GLOBAL STATUS LIKE 'Threads_running'
51 Query	EXPLAIN SELECT /*!40001 SQL_NO_CACHE */ `emp_no` FROM `employees`.`employees_ptchksum` FORCE INDEX(`PRIMARY`) WHERE ((`emp_no` >= '474878')) ORDER BY `emp_no` LIMIT 291764, 2 /*next chunk boundary*/
51 Query	SELECT /*!40001 SQL_NO_CACHE */ `emp_no` FROM `employees`.`employees_ptchksum` FORCE INDEX(`PRIMARY`) WHERE ((`emp_no` >= '474878')) ORDER BY `emp_no` LIMIT 291764, 2 /*next chunk boundary*/
51 Query	SELECT /*!40001 SQL_NO_CACHE */ `emp_no` FROM `employees`.`employees_ptchksum` FORCE INDEX(`PRIMARY`) ORDER BY `emp_no` DESC LIMIT 1 /*last upper boundary*/
51 Query	EXPLAIN SELECT COUNT(*) AS cnt, COALESCE(LOWER(CONV(BIT_XOR(CAST(CRC32(CONCAT_WS('#', `emp_no`, `birth_date`, convert(`first_name` using utf8mb4), convert(`last_name` using utf8mb4), convert(`gender` using utf8mb4), `hire_date`)) AS UNSIGNED)), 10, 16)), 0) AS crc FROM `employees`.`employees_ptchksum` FORCE INDEX(`PRIMARY`) WHERE ((`emp_no` >= '474878')) AND ((`emp_no` <= '499999')) /*explain checksum chunk*/
51 Query	REPLACE INTO `percona`.`checksums` (db, tbl, chunk, chunk_index, lower_boundary, upper_boundary, this_cnt, this_crc) SELECT 'employees', 'employees_ptchksum', '4', 'PRIMARY', '474878', '499999', COUNT(*) AS cnt, COALESCE(LOWER(CONV(BIT_XOR(CAST(CRC32(CONCAT_WS('#', `emp_no`, `birth_date`, convert(`first_name` using utf8mb4), convert(`last_name` using utf8mb4), convert(`gender` using utf8mb4), `hire_date`)) AS UNSIGNED)), 10, 16)), 0) AS crc FROM `employees`.`employees_ptchksum` FORCE INDEX(`PRIMARY`) WHERE ((`emp_no` >= '474878')) AND ((`emp_no` <= '499999')) /*checksum chunk*/
51 Query	SHOW WARNINGS
51 Query	SELECT this_crc, this_cnt FROM `percona`.`checksums` WHERE db = 'employees' AND tbl = 'employees_ptchksum' AND chunk = '4'
51 Query	UPDATE `percona`.`checksums` SET chunk_time = '0.036433', master_crc = '6ca4b1c9', master_cnt = '25122' WHERE db = 'employees' AND tbl = 'employees_ptchksum' AND chunk = '4'

-- 上述校驗到chunk = 4其實已經包含整張表的資料,以下的chunk範圍為emp_no < 10001和emp_no > 4999999,這樣做的意圖很明顯,因為工具並不能確認從庫在這兩個chunk所對應的資料範圍內沒有資料存在,為了保證檢查資料的完整性,所以進行這樣的檢查操作,包括了所有的可能性。

'對應emp_no < 10001'
51 Query	SHOW GLOBAL STATUS LIKE 'Threads_running'
51 Query	EXPLAIN SELECT  COUNT(*), '0' FROM `employees`.`employees_ptchksum` FORCE INDEX(`PRIMARY`) WHERE ((`emp_no` < '10001')) ORDER BY `emp_no` /*explain past lower chunk*/
51 Query	REPLACE INTO `percona`.`checksums` (db, tbl, chunk, chunk_index, lower_boundary, upper_boundary, this_cnt, this_crc) SELECT 'employees', 'employees_ptchksum', '5', 'PRIMARY', NULL, '10001', COUNT(*), '0' FROM `employees`.`employees_ptchksum` FORCE INDEX(`PRIMARY`) WHERE ((`emp_no` < '10001')) ORDER BY `emp_no` /*past lower chunk*/
51 Query	SHOW WARNINGS
51 Query	SELECT this_crc, this_cnt FROM `percona`.`checksums` WHERE db = 'employees' AND tbl = 'employees_ptchksum' AND chunk = '5'
51 Query	UPDATE `percona`.`checksums` SET chunk_time = '0.062096', master_crc = '0', master_cnt = '0' WHERE db = 'employees' AND tbl = 'employees_ptchksum' AND chunk = '5'

'對應emp_no > 499999'
51 Query	SHOW GLOBAL STATUS LIKE 'Threads_running'
51 Query	EXPLAIN SELECT  COUNT(*), '0' FROM `employees`.`employees_ptchksum` FORCE INDEX(`PRIMARY`) WHERE ((`emp_no` > '499999')) ORDER BY `emp_no` /*explain past upper chunk*/
51 Query	REPLACE INTO `percona`.`checksums` (db, tbl, chunk, chunk_index, lower_boundary, upper_boundary, this_cnt, this_crc) SELECT 'employees', 'employees_ptchksum', '6', 'PRIMARY', '499999', NULL, COUNT(*), '0' FROM `employees`.`employees_ptchksum` FORCE INDEX(`PRIMARY`) WHERE ((`emp_no` > '499999')) ORDER BY `emp_no` /*past upper chunk*/
51 Query	SHOW WARNINGS
51 Query	SELECT this_crc, this_cnt FROM `percona`.`checksums` WHERE db = 'employees' AND tbl = 'employees_ptchksum' AND chunk = '6'
51 Query	UPDATE `percona`.`checksums` SET chunk_time = '0.002064', master_crc = '0', master_cnt = '0' WHERE db = 'employees' AND tbl = 'employees_ptchksum' AND chunk = '6'

51 Query	SHOW GLOBAL STATUS LIKE 'Threads_running'
51 Query	SHOW MASTER STATUS
51 Quit	

根據general log可以看出,pt-table-checksum主要工作的流程可以總結如下:

  1. 連線主庫和從庫,查詢當前資料庫伺服器資訊,包括引數設定,負載資訊等;
  2. 根據工具選項設定會話級別引數,設定會話級binlog row formatSTATEMENT
  3. 根據工具選項建立校驗結果表(預設為percona.checksums表),檢視當前資料庫伺服器執行狀態;
  4. 獲取校驗的資料庫和表,逐張順序進行檢查校驗;
  5. 開始分析表,根據表的索引(如有),將表分成多個chunk,每個chunk包含多行,預設為1000,chunk對應的錶行數可以根據資料庫效能狀態動態調整;
  6. 根據以上分析生成表的校驗語句,並檢查表結構;
  7. 開始進行表的校驗分析,為了保證一致性,這個階段會將當前chuck所包含的行加上行鎖,並將校驗的結果以replace into方式存入校驗結果表;
  8. 再次查詢校驗結果表,並更新master_crcmaster_cnt的值(主庫),如果是從庫則是this_crcthis_cnt
  9. 根據資料庫執行狀態調整下一個chunk所包含行數;
  10. 繼續下一個chunk的校驗檢查,直到表中所有的chunk校驗完成;
  11. 從庫執行完校驗檢查,彙總這張表的結果;
  12. 迴圈完成所有需要校驗的表直到完成所有的表的校驗操作。
回到頂部

總結

  1. pt-table-checksum是當前MySQL主從資料庫資料一致性校驗比較好的工具,但也會對伺服器效能造成影響,儘管影響的範圍有限,在進行每個chunk檢查校驗時會對chunk中涉及的錶行加鎖,所以,檢驗還是儘量在業務低峰期進行操作;
  2. pt-table-checksum工具無法對主從表結構不一致的表進行校驗;
  3. 校驗結果只能看出哪些表出現不一致性,並無法得出具體出現多少行資料不一致,哪些資料行不一致等。
回到頂部

參考

https://www.percona.com/doc/percona-toolkit/LATEST/pt-table-checksum.html
http://seanlook.com/2015/12/29/mysql_replica_pt-table-checksum/
http://keithlan.github.io/2016/05/25/pt_table_checksum/

☆〖本人水平有限,文中如有錯誤還請留言批評指正!〗☆