pt table checksum Verify MySQL replication integrity. pt table checksum performs an online replication consistency check by executing checksum queries ...
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
指定校驗操作使用的哈希函數。可選函數有SHA1、MD5等。
--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
預設值:processlist,hosts
指定獲取從庫的方式。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方式找到slave,hosts方式要求從庫配置'--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
指定的操作。
使用限制
- pt-table-checksum工具要求主從複製是基於STATEMENT格式進行的複製,並且會在主庫執行
binlog_format=STATEMENT
語句。由於MySQL的限制,這一設置並不會傳遞到從庫進行。因此當從庫的binlog格式是ROW時,無法直接對從庫的從庫進行校驗操作。工具在進行校驗操作時都會檢查所有從庫的binlog_format,可以使用選項--[no]check-binlog-format
進行控制; - 工具假設主從資料庫中SCHEMA和表結構都是一致的。如果從庫上不存在主庫的SCHEMA,或者是從庫上表結構與主庫不一致,則有可能導致主從複製中斷。
用法示例
環境與數據準備
MySQL:5.7.24
MASTER:192.168.58.3:3306
SLAVE:192.168.58.5:3306
本文基於MySQL官方示例資料庫employee:Example 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 [email protected]: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
值只能為hosts或dsn,以下分兩種情況討論:
-- 指定--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 [email protected]: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 [email protected]: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
值為processlist或dsn。
-- 指定--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
值的設置):
- 對於所有類型的埠設置,dsn方式都可以使用,最為方便,也便於進行管理;
- 如果要使用hosts方式,則從庫必須配置參數
report_host
和report_port
;- 主庫如果是標準埠(3306),則只能使用hosts或dsn方式;
- 主庫如果是非標準埠(非3306),則根據從庫是否配置參數
report_host
和report_port
來決定:
有配置可選擇hosts方式;
沒有配置則只能選擇processlist或dsn方式。
- 執行整個資料庫校驗
# 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主要工作的流程可以總結如下:
- 連接主庫和從庫,查詢當前資料庫伺服器信息,包括參數設置,負載信息等;
- 根據工具選項設置會話級別參數,設置會話級
binlog row format
為STATEMENT;- 根據工具選項創建校驗結果表(預設為percona.checksums表),查看當前資料庫伺服器運行狀態;
- 獲取校驗的資料庫和表,逐張順序進行檢查校驗;
- 開始分析表,根據表的索引(如有),將表分成多個chunk,每個chunk包含多行,預設為1000,chunk對應的表行數可以根據資料庫性能狀態動態調整;
- 根據以上分析生成表的校驗語句,並檢查表結構;
- 開始進行表的校驗分析,為了保證一致性,這個階段會將當前chuck所包含的行加上行鎖,並將校驗的結果以
replace into
方式存入校驗結果表;- 再次查詢校驗結果表,並更新master_crc、master_cnt的值(主庫),如果是從庫則是this_crc、this_cnt;
- 根據資料庫運行狀態調整下一個chunk所包含行數;
- 繼續下一個chunk的校驗檢查,直到表中所有的chunk校驗完成;
- 從庫運行完校驗檢查,彙總這張表的結果;
- 迴圈完成所有需要校驗的表直到完成所有的表的校驗操作。
總結
- pt-table-checksum是當前MySQL主從資料庫數據一致性校驗比較好的工具,但也會對伺服器性能造成影響,儘管影響的範圍有限,在進行每個chunk檢查校驗時會對chunk中涉及的表行加鎖,所以,檢驗還是儘量在業務低峰期進行操作;
- pt-table-checksum工具無法對主從表結構不一致的表進行校驗;
- 校驗結果只能看出哪些表出現不一致性,並無法得出具體出現多少行數據不一致,哪些數據行不一致等。
參考
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/
☆〖本人水平有限,文中如有錯誤還請留言批評指正!〗☆