技術分享 | 兩個單機 MySQL 該如何校驗資料一致性

愛可生雲資料庫發表於2022-01-31

作者:莫善

某網際網路公司高階 DBA。

本文來源:原創投稿

*愛可生開源社群出品,原創內容未經授權不得隨意使用,轉載請聯絡小編並註明來源。


需求介紹

業務有兩個 MySQL 叢集是通過 MQ 進行同步的,昨晚 MQ 出現異常,報了很多主鍵衝突,想請 dba 幫忙校驗一下兩個叢集的資料是否一致。

一、前言

當接到這個需求的時候並沒當回事,隱約有點印象 pt-table-checksum 能通過 dsn 實現 MySQL 的資料校驗,所以當時就應承下來了。不曾想,啪啪打臉,回想起來真是草率了。

本文參考的是 pt-table-checksum 的校驗邏輯,基於資料塊去遍歷每個表,然後比對 checksum 的值判斷該塊是否一致,本文主要是想聊聊我在實現資料校驗指令碼過程中遇到的問題以及解決思路,希望對大家有幫助。

二、測試 dsn

利用線上的配置檔案搭建一套主從環境。

  • 搭建步驟略
  • 本例使用 mysql 5.7.26 進行測試
  • 本例使用 percona-toolkit-3.2.1 進行測試

1、校驗主從資料一致性

這個用例將通過 dsn 方式連線從庫。

配置 dsn 過程略
$ ./bin/pt-table-checksum h='192.168.1.1',u='mydba',p='test123456',P=6666 --nocheck-replication-filters --replicate=test.checksums --no-check-binlog-format -d dbatest1  --recursion-method dsn=t=percona_schema.dsns
Checking if all tables can be checksummed ...
Starting checksum ...
            TS ERRORS  DIFFS     ROWS  DIFF_ROWS  CHUNKS SKIPPED    TIME TABLE
01-13T17:48:20      0      0        0          0       1       0   0.377 dbatest1.dbatest
可以看到測試通過,能正常做校驗。

2、校驗非主從資料一致性

這個用例將通過 dsn 方式連線從庫,但是會將從庫的複製鏈路 stop 掉,並清空複製資訊。

mysql> stop slave;
Query OK, 0 rows affected (0.01 sec)

mysql> reset slave all;
Query OK, 0 rows affected (0.01 sec)

mysql> 

$ ./bin/pt-table-checksum h='192.168.1.1',u='mydba',p='test123456',P=6666 --nocheck-replication-filters --replicate=test.checksums --no-check-binlog-format -d dbatest1  --recursion-method dsn=t=percona_schema.dsns
Checking if all tables can be checksummed ...
Starting checksum ...
Replica mysql2 is stopped.  Waiting.
Replica mysql2 is stopped.  Waiting.
Replica mysql2 is stopped.  Waiting.
Replica mysql2 is stopped.  Waiting.
直接翻車,可以看到校驗會提示從庫【is stopped. Waiting】,這就尷尬了,pt-table-checksum 居然不支援非主從環境的資料校驗,既然如此那隻能另想他法了。

三、開發工具遇到的問題

1、解決複雜的聯合主鍵問題

(1)查詢索引失效,或者查詢報錯問題

熟悉 pt-table-checksum 的朋友應該都知道,該工具是基於主鍵(非空唯一鍵)進行掃描資料行,其實這個邏輯針對整型單列主鍵實現起來很簡單,但是如果是聯合主鍵且是字元型,好像就沒那麼簡單了,有興趣的可以思考一下。下面我先說一下大致的邏輯:

第一步:判斷 _min_rowid 是否為空,為空就取該表的第一行,並記作 _min_rowid 。

if [ -z "${_min_rowid}" ]
then #拿出當前表的最小行id, 每次拿資料的起始行
    _min_rowid="$(${mysql_comm} -NBe "select min(pk) from table")"
fi

第二步:根據 _min_rowid 作為條件進行掃描該表,取下一個資料塊的資料,記錄資料塊的最後一行資料的主鍵值,記錄 checksum 的值,並記下 _min_rowid 。

select * from table where pk > ${_min_rowid} limit checksize #計算這個塊的checksum值
_min_rowid="$(${mysql_comm} -NBe "select max(pk) from (select pk from table where pk > ${_min_rowid} order by pk limit checksize)a")" #記錄下一個最小行pk值

第三步:判斷_min_rowid是否為空,非空重複第二步,為空退出檢查。

if [ -z "${_min_rowid}" ]
then
    break
else
    continue
fi

通過上述三個步驟可以看到,如果是單列整型的主鍵,實現起來很簡單,但是問題來了,業務的表的主鍵五花八門,有的是聯合主鍵,有的是字元型的聯合主鍵,還有整型+字元型的聯合主鍵,那麼上述的實現方式顯然是有問題的。所以實現起來需要多考慮幾個問題:

  • 需要考慮主鍵是否是聯合主鍵。

    如果是聯合主鍵,在取資料塊的時候查詢條件就是 where pk1 > xxx and pk2 > yyy
  • 需要考慮主鍵欄位的資料型別是否是整型或字元型。

    如果主鍵欄位是字元型,在取資料塊的時候查詢條件就是 where pk > 'xxx' ,否則查詢將不會使用到索引。

鑑於存在上述兩個問題,可以參考如下實現邏輯:

  • 獲取主鍵欄位列表,放在陣列裡
pri_name=($(${mysql_comm} -NBe "select COLUMN_NAME from information_schema.columns where table_name = 'table' and table_schema = 'db' and COLUMN_KEY = 'PRI';"))
  • 根據主鍵欄位名獲取欄位的資料型別,放在關聯陣列裡
 for tmp in ${pri_name[@]}
 do #將各個主鍵欄位的資料型別記錄到字典, 後文會判斷主鍵欄位的型別是否是字元型, 如果是字元型就需要特殊處理(帶引號)
     __="select DATA_TYPE from information_schema.columns where table_schema = 'db' and table_name = 'table' and COLUMN_KEY = 'PRI' and COLUMN_NAME = '${tmp}'"
     pri_type["${tmp}"]="$(${mysql_comm} -NBe "${__}" 2>/dev/null)"
 done
  • 根據欄位的資料型別,如果是字元型就需要做特殊處理
for tmp in ${pri_name[@]}
do #這步主要是解決將主鍵弄清楚, 到底是單列主鍵還是多列, 到底是整型還是其他, 然後根據不同的型別拼接成一個字串, 主要是用來作為後面取數是否要加單引號
   #因為整型取出來不用做特殊處理, 但是非整型需要加上單引號, 要不然作為where條件對比的時候肯定有問題
    if [ "$(grep -ic "int" <<< "${pri_type["${tmp}"]}")x" != "1x" ]
    then
        select_str_tmp="concat(\"'\",${tmp},\"'\")"
        pk_style="str"
    else
        select_str_tmp="${tmp}"
    fi

    if [ -z "${select_str}" ]
    then
        select_str="${select_str_tmp}"
    else
        select_str="${select_str},${select_str_tmp}"
    fi
done

這步的作用是說在取每個塊的資料,需要記錄 _min_rowid 的時候會根據主鍵的型別記錄不一樣的值,比如 :

  • 整型就記錄成_min_rowid=1
  • 字元型就記錄成_min_rowid='1'
  • 整型 + 字元型的聯合主鍵就記錄成_min_rowid=1,'1'
  • 字元型的聯合主鍵就記錄成_min_rowid='1','2'

這樣在每次取資料塊的時候where後面的條件既能正確的使用索引,也不至於因為是非整形而沒有帶上引號而報錯。

(2)如何界定每個資料塊的左區間的邊界

假如有這麼一個聯合主鍵欄位 primary key(a,b,c) 都是整型,該如何編寫遍歷 sql 呢?起初我的想法很簡單,具體如下:

_min_rowid=(xxx,yyy,zzz)
select * from where 1 = 1 and a >= xxx and b >= yyy and c > zzz order by a,b,c limit checksize
乍一看好像邏輯沒問題,但是實際跑指令碼的時候發現這個邏輯不能完全掃完全表,後來經過多次測試校驗,得出下面的邏輯sql
_min_rowid=(xxx,yyy,zzz)
select * from where 1 = 1 and ((a > xxx) or (a = xxx and b > yyy) or (a = xxx and b = yyy and c > zzz)) order by a,b,c limit checksize

至此在編寫校驗指令碼過程遇到的兩個問題就算告一段落了,剩下的就是各種邏輯處理了,不過多贅述,有興趣的可以自行閱讀指令碼檔案。

四、資料校驗工具做了哪些改動

1、取消 for update

本著最低程度影響業務,所以取消加鎖邏輯。但是又要保證該資料塊的資料一致性,如果這個資料塊是個熱資料,當前正在變更,那麼校驗的時候難免會不一致。所以只能通過多次校驗實現,預設是校驗20次,其中有一次校驗結果是一致,就認為是一致的,如果前5次校驗過程中,這個資料塊的資料沒有變化,也視為不一致(可能是因為延遲,也可能是真的不一致)。

另外 checksum 狀態是寫到臨時檔案而非寫到業務資料庫。

2、支援表結構校驗

pt-table-checksum 不校驗表結構,改寫時新增表結構的校驗。

3、支援基於表的並行校驗

可以基於表的並行校驗,可由使用者指定並行數,但是指令碼有個安全機制,如果使用者指定的並行數大於當前 cpu 空閒核心數,就會按當前(空閒核心數-1)作為並行數。

4、支援網路監控

新增網路監控,由使用者指定網路上限百分比,當網路卡流量超過這個百分比就暫停任務,等待網路卡流量低於閾值才會繼續任務。這個主要是出於對於中介軟體(mycat)的場景或者分散式資料庫(tidb)的場景。

5、支援定時任務功能

支援定時任務功能,使用者可以使用這個功能規避業務高峰,僅在業務低峰進行資料校驗。

指定了時間段執行校驗任務,如果當天沒校驗完成,等到次日會繼續校驗。

6、支援任意兩個節點的校驗

不僅限於主從節點的校驗,只要目標物件支援 MySQL 的標準 SQL 語法就能做資料校驗。

7、新增超時機制及自殺機制

校驗邏輯是通過 SQL 採集目標節點的資料庫,如果目標資料庫系統當前存在異常,無疑是雪上加霜,將會觸發未知問題,所以新增超時機制,單次取資料塊的閾值是5s,超過5秒就放棄等待重試。測試發現,有時候即便觸發超時了,但是 SQL 任務還是會在目標資料庫的 processlist 中能看到,所以又新增了一個 kill 機制,超時後會觸發一個 kill processlist id 的動作。另外為了避免 kill 錯,在每個 SQL 物件新增了一個32位的 md5 值,每次 kill 的時候會校驗這個 md5 值。

保留 threads_running 的監控,如果 threads_running 過大就會暫停校驗,這部分監控邏輯是跟網路監控一起

五、資料校驗工具使用介紹

本工具借鑑 pt-table-checksum 工具思路改寫,可以檢查隨意兩個 mysql(支援 mysql sql 語法的資料庫)節點的資料一致性。

<font color='red'>本工具僅供學習使用,如需檢查線上的資料,請充分測試</font>

1、校驗邏輯

基於主鍵以一個塊遍歷資料表,比對checksum的值,塊的大小可通過引數指定。
(1)獲取該表的第一個資料塊的查詢SQL。
(2)將兩個目標節點的資料塊的checksum的值,記錄到臨時檔案,file1 file2。
(3)比對file1 file2是否一致。

  • 不一致 : 重複(2)的操作,至多連續20次,還不一致會將該SQL記錄到table目錄
  • 一致 : 跳到(4)
  • file1為空 : 表示該表遍歷完成,直接跳到(5)
    (4)獲取該表的下一個資料塊的查詢SQL。
    (5)檢查通過就跳到(7),檢查不通過調到(6)。
    (6)讀取table目錄校驗不通過的SQL進行再次校驗。
  • 本次校驗通過也視為資料一致
  • 如果校驗不通過,會將不一致的部分記錄到diff目錄
    (7)該表校驗任務結束。

2、功能介紹

  • 檢查隨意兩個幾點的資料一致性
  • 支援表結構的校驗
  • 支援併發檢查,基於表的併發
  • 支援指定時間,可以規避業務高峰期
  • 支援網路監控,如果網路超過閾值可以暫停校驗
  • <font color='red'>不支援無主鍵(非空唯一鍵)的表</font>
  • <font color='red'>不支援聯合主鍵達到四個欄位及以上的表</font>

3、安裝教程

(1)下載
git clone https://gitee.com/mo-shan/check_data_for_mysql.git
cd check_data_for_mysql
(2)配置
  • 編輯配置檔案
cd /path/check_data_for_mysql
vim conf/check.conf
請結合實際情況根據註釋提示進行相關配置
  • 修改工作路徑
sed -i 's#^work_dir=.*#work_dir=\"/check_data_for_mysql_path\"#g' start.sh #將這裡的check_data_for_mysql_path改成check_data_for_mysql的家目錄的絕對路徑

4、使用說明

(1)目錄介紹
moshan /data/git/check_data_for_mysql > tree -L 2
.
├── conf
│   └── check.conf
├── func
│   ├── f_check_diff_for_mysql.sh
│   ├── f_check_diff_for_row.sh
│   ├── f_logging.sh
│   └── f_switch_time.sh
├── log
├── manager.sh
├── README.en.md
├── README.md
└── start.sh

3 directories, 9 files
moshan /data/git/check_data_for_mysql >
  • conf 配置檔案的目錄,check.conf 是配置檔案
  • log 日誌目錄
  • start.sh 主程式
  • manager.sh 網路監控指令碼,任務狀態的管理指令碼
  • func 目錄是存放指令碼的目錄

    • f_check_diff_for_mysql.sh 校驗資料塊的指令碼
    • f_check_diff_for_row.sh 校驗資料行,這個指令碼是將f_check_diff_for_mysql.sh校驗不通過的結果做進一步校驗
(2)幫助手冊
  • 主程式
moshan /data/git/check_data_for_mysql > bash start.sh
Usage: start.sh

                 [ -t check_table ]             需要檢查的表列表, 預設整庫
                 [ -T skip_check_table ]        不需要檢查的表, 預設不過濾
                 [ -d check_db ]                需要檢查的庫, 預設是除了系統庫以外的所有庫
                 [ -D skip_check_db ]           不需要檢查的庫, 預設不過濾
                 [ -w threads ]                 最大併發數
                 [ -l limit_time ]              哪些時間段允許跑校驗, 預設是所有時間, 如需限制可以使用該引數進行限制, 多個時間用英文逗號隔開
                                                1-5,10-15   表示1點到5點(包含1點和5點), 或者10點到15點可以跑, 需要注意都是閉區間的
                                                1,5,10,15   表示1點, 5點, 10點, 15點可以跑
                 [ -f true ]                    是否執行check操作, 預設是false, 只有為true的時候才會check
                 [ -h ]                         幫助資訊

moshan /data/git/check_data_for_mysql >
可以根據需求進行引數使用,<font color='red'>如需規避業務高峰期在低峰執行校驗任務,請使用-l引數指定執行時間 ,如'-l 1-5'表示凌晨1點到5點執行校驗任務,如果當天六點前沒校驗完成,會等到次日凌晨1點繼續校驗</font>
  • 任務管理指令碼
moshan /data/git/check_data_for_mysql > bash manager.sh -h

Usage: manager.sh

                 [ -a start|stop|continue|pause ]     監控任務的管理動作, 資料校驗任務的管理動作
                                                      start : 啟動網路監控
                                                      stop|pause|continue : 連同校驗指令碼一起停掉|暫停|繼續

                 [ -t eth0 ]                          網路卡裝置名, 預設是eth0
                 [ -n 50 ]                            網路卡流量超過這個百分比就暫停, 等網路卡流量小於這個就繼續, 預設是50
                 [ -h ]                               幫助資訊


moshan /data/git/check_data_for_mysql > 

可以根據實際網路卡資訊針對該網路卡進行監控,當流量達到指定的閾值就會暫時暫停資料校驗。這個指令碼主要是針對使用了中介軟體,比如mycat(mysql到mycat)。或者是tidb(tikv到tidb),這種情況下回佔用較多網路頻寬。

  • <font color='red'>該指令碼必須要求在整個工具的家目錄下執行</font>
(3)常用命令參考
  • 管理指令碼相關

    • bash manager.sh -a start -t eth0 -n 30 啟動eth0網路卡的流量監控,流量達到30%就暫停資料校驗
    • bash manager.sh -a pause 暫停監控及暫停資料校驗任務
    • bash manager.sh -a continue 繼續監控及繼續資料校驗
    • bash manager.sh -a stop 停止監控及停止資料校驗
  • 主程式相關

    • bash start.sh -f true -d dbatest -t test1 -l 0-5 僅校驗dbatest庫下的test表,且在0點到5點執行校驗任務
(4)測試用例-校驗通過場景
  • <font color='red'>每次執行校驗任務的時候強制要清空log目錄,所以請做好校驗結果的備份</font>
  • <font color='red'>執行校驗任務的時候強烈建議開啟screen</font>
  • <font color='red'>有網路卡監控需求,執行監控指令碼時也強烈建議單獨開啟 screen 進行監控 </font>

第一步:先開啟一個 screen 監控網路

moshan /data/git/check_data_for_mysql > screen -S check_net_3306
moshan /data/git/check_data_for_mysql > bash manager.sh -a start
[ 2022-01-18 11:55:34 ] [ 1000 Mb/s ] [ RX : 2    MB/S ]  [ TX : 2    MB/S ]
[ 2022-01-18 11:55:35 ] [ 1000 Mb/s ] [ RX : 2    MB/S ]  [ TX : 4    MB/S ]
[ 2022-01-18 11:55:36 ] [ 1000 Mb/s ] [ RX : 2    MB/S ]  [ TX : 2    MB/S ]
[ 2022-01-18 11:55:37 ] [ 1000 Mb/s ] [ RX : 2    MB/S ]  [ TX : 3    MB/S ]
[ 2022-01-18 11:55:38 ] [ 1000 Mb/s ] [ RX : 1    MB/S ]  [ TX : 2    MB/S ]
[ 2022-01-18 11:55:39 ] [ 1000 Mb/s ] [ RX : 1    MB/S ]  [ TX : 2    MB/S ]
[ 2022-01-18 11:55:41 ] [ 1000 Mb/s ] [ RX : 1    MB/S ]  [ TX : 2    MB/S ]
[ 2022-01-18 11:55:42 ] [ 1000 Mb/s ] [ RX : 2    MB/S ]  [ TX : 8    MB/S ]

第二步:新開啟一個screen執行校驗任務

moshan /data/git/check_data_for_mysql > screen -S check_data_3306
moshan /data/git/check_data_for_mysql > bash start.sh -d dba -t dbatest1 -f true 
[ 2022-01-17 20:32:19 ] [ 成功 ] [ 192.168.1.1 ] [ start.sh/start.sh ] [ f_prepare:130 ] [ 本次資料一致性檢查開始 ]
[ 2022-01-17 20:32:19 ] [ 警告 ] [ 192.168.1.1 ] [ start.sh/start.sh ] [ f_main:185 ] [ 本次資料一致性檢查將檢查如下庫 : [dba] ]
[ 2022-01-17 20:32:19 ] [ 成功 ] [ 192.168.1.1 ] [ start.sh/start.sh ] [ f_main:203 ] [ 正在檢查dba庫 ]

[ 2022-01-17 20:32:19 ] [ 成功 ] [ 192.168.1.1 ] [ func/f_check_diff_for_mysql.sh ] [ f_check_diff_for_mysql:249 ] [ dba.dbatest1 ] [ 表結構一致 ]

[ 2022-01-17 20:32:19 ] [ 成功 ] [ 192.168.1.1 ] [ func/f_check_diff_for_mysql.sh ] [ f_check_diff_for_mysql:491 ] [ dba.dbatest1 ] [ 1,1 ] [ 00 d 00 h 00 m 00 s ] [ 9.09%, (0:0)/1 ] [ 資料一致 ]
[ 2022-01-17 20:32:19 ] [ 成功 ] [ 192.168.1.1 ] [ func/f_check_diff_for_mysql.sh ] [ f_check_diff_for_mysql:491 ] [ dba.dbatest1 ] [ 2,11 ] [ 00 d 00 h 00 m 00 s ] [ 100.00%, (0:0)/1 ] [ 資料一致 ]
[ 2022-01-17 20:32:19 ] [ 成功 ] [ 192.168.1.1 ] [ func/f_check_diff_for_mysql.sh ] [ f_check_diff_for_mysql:504 ] [ dba.dbatest1 ] [ 檢查完畢 ]

[ 2022-01-17 20:32:19 ] [ 成功 ] [ 192.168.1.1 ] [ start.sh/start.sh ] [ f_main:242 ] [ 本次資料一致性檢查完成 ] [ 通過 ]

moshan /data/git/check_data_for_mysql > 
檢查結束後會提示檢查通過,否則就是檢查不通過,如下面的用例。
(5)測試用例-校驗不通過場景
  • 執行校驗任務的時候強烈建議開啟 screen

    moshan /data/git/check_data_for_mysql > screen -S check_data_3306
    moshan /data/git/check_data_for_mysql > bash start.sh -d dbatest1 -f true 
    [ 2022-01-17 20:32:09 ] [ 成功 ] [ 192.168.1.1 ] [ start.sh/start.sh ] [ f_prepare:130 ] [ 本次資料一致性檢查開始 ]
    [ 2022-01-17 20:32:09 ] [ 警告 ] [ 192.168.1.1 ] [ start.sh/start.sh ] [ f_main:185 ] [ 本次資料一致性檢查將檢查如下庫 : [dbatest1] ]
    [ 2022-01-17 20:32:09 ] [ 成功 ] [ 192.168.1.1 ] [ start.sh/start.sh ] [ f_main:203 ] [ 正在檢查dbatest1庫 ]
    
    [ 2022-01-17 20:32:09 ] [ 警告 ] [ 192.168.1.1 ] [ func/f_check_diff_for_mysql.sh ] [ f_check_diff_for_mysql:242 ] [ dbatest1.dbatest ] [ 表結構不一致 ] [ a_time name ] [ 跳過該表的檢查 ]
    
    
    [ 2022-01-17 20:32:09 ] [ 錯誤 ] [ 192.168.1.1 ] [ start.sh/start.sh ] [ f_main:232 ] [ 本次資料一致性檢查完成 ] [ 不通過 ]
    
    [ 2022-01-17 20:32:09 ] [ 錯誤 ] [ 192.168.1.1 ] [ start.sh/start.sh ] [ f_main:237 ] [ dbatest1.dbatest:table structure err ]
    moshan /data/git/check_data_for_mysql >

    5、測試結果解讀

moshan /data/git/check_data_for_mysql > ls -l
total 444
-rw-r--r-- 1 root root 450389 Jan 18 11:56 info.log
drwxr-xr-x 2 root root    194 Jan 18 11:56 list
drwxr-xr-x 2 root root      6 Jan 18 11:56 md5
drwxr-xr-x 6 root root     72 Jan 18 11:56 pri
drwxr-xr-x 5 root root     42 Jan 18 11:52 res
-rw-r--r-- 1 root root     65 Jan 18 11:56 skip.log
moshan /data/git/check_data_for_mysql > 

(1)info.log 檔案

校驗的日誌,會將資料庫的資料是否一致一一記錄,如下是一行日誌記錄。
[ 2022-01-17 20:32:19 ] [ 成功 ] [ 192.168.1.1 ] [ func/f_check_diff_for_mysql.sh ] [ f_check_diff_for_mysql:491 ] [ dba.dbatest1 ] [ 2,11 ] [ 00 d 00 h 00 m 00 s ] [ 100.00%, (0:0)/1 ] [ 資料一致 ]
  • [ 2022-01-17 20:32:19 ] 第一段是記錄日誌的時間
  • [ 成功 ] 第二段是日誌狀態
  • [ 192.168.1.1 ] 第三段是產生日誌的機器 ip
  • [ func/f_check_diff_for_mysql.sh ] 第四段是哪個檔案產生的日誌
  • [ f_check_diff_for_mysql:491 ] 第五段是哪個函式:行號產生的日誌
  • [ dba.dbatest1 ] 第六段是針對哪個 db 哪個表產生的日誌
  • [ 2,11 ] 第七段是資料塊的左右閉區間
  • [ 00 d 00 h 00 m 00 s ] 第八段是針對該表的資料校驗總執行的時間
  • [ 100.00%, (0:0)/1 ] 第九段是執行進度,其中小括號部分表示:(校驗通過的表個數:校驗不通過的表個數)/總共需要校驗的表的個數
  • [ 資料一致 ] 第十段是資料一致狀態。

(2)list目錄

-rw-r--r-- 1 root root  77 Jan 18 11:52 dba_ing.list
-rw-r--r-- 1 root root  77 Jan 18 11:56 dba.list
這個目錄會針對每個 db 記錄兩個檔案,一個是已經校驗通過的表,另一個是正在校驗的表。

(3)md5 目錄

儲存資料塊的 checksum 臨時目錄,可以忽略

(4)pri 目錄

這個目錄會針對每個 db 都建立一個目錄,然後記錄每個表當前校驗的資料塊的最後一行資料的 pk(pk list)值

(5)res 目錄

這個目錄是記錄校驗結果的目錄,會有三個子目錄
drwxr-xr-x 2 root root 6 Jan 18 11:52 diff
drwxr-xr-x 2 root root 6 Jan 18 11:52 row
drwxr-xr-x 2 root root 6 Jan 18 11:56 table
  • table : f_check_diff_for_mysql.sh 指令碼會將校驗不通過的資料塊的SQL記錄在這裡。這個目錄會按db建立目錄,將記錄校驗不通過的資料塊的SQL語句格式如下:"table/db/table.log"
  • row : f_check_diff_for_row.sh 指令碼會讀取table目錄的SQL語句進行再次校驗,然後產生的的臨時檔案存在row目錄,可以忽略
  • diff : f_check_diff_for_row.sh 指令碼會讀取table目錄的SQL語句進行再次校驗,然後產生的再次校驗不通過的部分存記錄到這個目錄,格式如下:"diff/db.table.num.diff"

這是 table 目錄下記錄某個資料塊不一致的一個例子

set tx_isolation='REPEATABLE-READ';set innodb_lock_wait_timeout=1;SELECT '127d04065afd91d587bbb19bc16037a6:mobile_bind', COUNT(*) AS cnt, COALESCE(LOWER(CONV(BIT_XOR(CAST(CRC32(CONCAT_WS('#',`id`,`uid`,`count`,`score`,`timestamp`,`info`,`mobile_info`,`del`,CONCAT(ISNULL(`id`),ISNULL(`uid`),ISNULL(`count`),ISNULL(`score`),ISNULL(`timestamp`),ISNULL(`info`),ISNULL(`mobile_info`),ISNULL(`del`)))) AS UNSIGNED)), 10, 16)), 0) AS crc FROM (select * from tdb_spam_oltp.`mobile_bind` where 1 = 1 and id > 667930554 order by id limit 10000 )a

如果校驗某個資料塊發現兩個節點資料不一致會記錄這個 SQL

  • 一來是方便f_check_diff_for_row.sh指令碼再次校驗
  • 二來是方便使用者再次確認是真的不一致還是因為這是熱資料,在校驗的時候正在頻繁被修改

這是 diff 目錄下記錄某個資料行不一致的一個例子

7974c7974
< 667930554    2    1642491495866595584    100    948895134572797275    2022-01-10 16:01:30    2022-01-11 10:45:04    {"dlvBoid":7877725947093058957}    -667930554
---
> 667930554    1    1642491495866595584    100    948895134572797275    2022-01-10 16:01:30    2022-01-10 16:32:01    {"dlvBoid":7877725947093058957}    -667930554
同一個主鍵,如果資料不一致會以這樣的格式記錄到 diff 目錄

(6)skip.log 檔案

檢查不通過在log目錄都會生成一個 skip.log 檔案, 裡面記錄了哪些表被跳過檢查及跳過原因,如果檢查通過就不會有這個檔案。
moshan /data/git/check_data_for_mysql > ls -l log/skip.log 
-rw-r--r-- 1 root root 37 Jan 17 20:35 log/skip.log
moshan /data/git/check_data_for_mysql > cat log/skip.log 
dbatest1.dbatest:table structure err
moshan /data/git/check_data_for_mysql >

六、寫在最後

本工具是參考了 pt-table-checksum 工具的一些思路並結合自身經驗進行改寫,尚有很多不足之處,僅做學習交流之用,<font color='red'>如有線上環境使用需求,請在測試環境充分測試。</font>

相關文章