記一次 MySQL 主從同步異常的排查記錄,百轉(zhuǎn)千回!
你好,我是悟空。
本文主要內(nèi)容如下:
目錄
一、現(xiàn)象
最近項目的測試環(huán)境遇到一個主備同步的問題:
備庫的同步線程停止了,無法同步主庫的數(shù)據(jù)更改。
備庫報錯如下:
從庫同步報錯信息
完整的錯誤信息:
上面的報錯信息是什么意思呢?
翻譯一下就是主庫的 binlog 或者從庫的 relay log 損壞了,造成這個問題的原因:
- 可能是網(wǎng)絡(luò)問題。
- 也可能是主庫或備庫的代碼 bug。
首先我們還是得復(fù)習(xí)下主從同步的原理才能更好地分析原因。
二、主從同步的原理
主從同步的原理
- 從庫會生成兩個線程,一個 I/O 線程,名字叫做 Slave_IO_Running,另外一個是 SQL 線程,名字叫做 Slave_SQL_Running;
- 從庫的 I/O 線程會去請求主庫的 binlog 日志文件,并將得到的 binlog 日志文件寫到本地的 relay log (中繼日志)文件中;
- 主庫會生成一個 dump 線程,用來給從庫 I/O 線程傳 binlog;
- 從庫 SQL 線程,會讀取 relay log 文件中的日志,并解析成 SQL 語句逐一執(zhí)行。
三、排查思路
3.1 分析從庫的同步狀態(tài)
我們可以打印下從庫的同步狀態(tài),看到如下幾個關(guān)鍵信息:
分析從庫的同步狀態(tài)
Master_Log_File: mysql-bin.000956,代表從庫讀到的主庫的 binlog file,
Read_Master_Log_Pos: 528071913,代表從庫讀到的主庫的 binlog file 的日志偏移量
Relay_Log_File: relay-bin.000094,代表從庫執(zhí)行到了哪一個 relay log
Relay_Log_Pos: 123408769,代表從庫執(zhí)行的 relay log file 的日志偏移量
Relay_Master_Log_File: mysql-bin.000955,代表從庫已經(jīng)重放到了主庫的哪個 binlog file。
Exec_Master_Log_Pos: 123408556,代表從庫已經(jīng)重放到了主庫 binlog file 的偏移量。
Slave_IO_Running: Yes,說明 I/O 線程正在運行,可以正常獲取 binlog 并生成 relay log。
Slave_SQL_Running: No,說明 SQL 線程已經(jīng)停止運行,不能正常解析 relay log,也就不能執(zhí)行主庫上已經(jīng)執(zhí)行的命令。
Master_Log_File? 和 Read_Master_Log_Pos 這兩個參數(shù)合起來表示的是讀到的主庫的最新位點。
Relay_Master_Log_File? 和 Exec_Master_Log_Pos,這兩個參數(shù)合起來表示的是從庫執(zhí)行的最新位點。
如果紅色框起來的兩個參數(shù):Master_Log_File? 和 Relay_Master_Log_File 相等,則說明從庫讀到的最新文件和主庫上生成的文件相同,這里前者是 mysql-bin.000956,后者是 mysql-bin.000955,說明兩者不相同,存在主從不同步。
如果藍(lán)色框起來的兩個參數(shù) Read_Master_Log_Pos? 和 Exec_Master_Log_Pos 相等,則說明從庫讀到的日志文件的位置和從庫上執(zhí)行日志文件的位置相同,這里不相等,說明主從不同步。
當(dāng)上面兩組參數(shù)都相等時,則說明主從同步正常,且沒有延遲。只要有任意一組不相等,則說明主從不同步,可能是從庫停止同步了,或者從庫存在同步延遲。由于上面的 SQL 線程已經(jīng)停止了,說明是從庫同步出現(xiàn)問題了。
從庫同步出現(xiàn)的問題在最開始的報錯信息里面已經(jīng)提到了,可能是網(wǎng)絡(luò)問題導(dǎo)致,還有可能是 binlog 或 relay log 損壞。
3.2 重啟萬能大法
先通過重啟來恢復(fù)從庫的 SQL 線程試試看?重啟方式就是兩種:
- 方式一:從庫重新開啟同步。就是執(zhí)行 stop slave; 和 start slave; 命令。
- 方式二:重啟從庫實例。就是重啟 mysql 實例或 mysql 容器。
這兩種方式試了后,都不能恢復(fù)從庫的 SQL 線程。
3.3 查看 binlog
再來看下 binlog 是否有損壞,在主庫上通過這個命令打開 mysql-bin.000955 文件。
沒有報錯信息,如下圖所示:
binlog 日志
3.4 查看 relay log
看到從庫同步的 Relay_Log_File 到 relay-bin.00094 就停止同步了,如下圖所示,可能是這個文件損壞了。
relay log 日志
在從庫上通過 mysqlbinlog 命令打開這個文件
可以看到有個報錯信息:
這段文字翻譯過來就是讀取錯誤,數(shù)據(jù)長度 7644,在讀取偏移量為 243899899 的日志時發(fā)生了錯誤,可能是日志文件格式錯誤或是讀取文件錯誤。
relay log 報錯信息
3.5 找原因
3.5.1 猜測事務(wù)日志太大
根據(jù)這個報錯信息可以知道這個事務(wù)日志數(shù)據(jù)太長了,data_len: 7644,而導(dǎo)致讀取錯誤。
而且上面還有很多 Update_rows 的操作。
猜測:會不會是主庫執(zhí)行了一個大事務(wù),造成該事務(wù)生成的一條 binlog 日志太大了,從庫生成的對應(yīng)的一條 relay log 日志也很大, SQL 線程去解析這條 relay log 日志解析報錯。
3.5.2 驗證
到主庫上查看下 binlog 日志里面有沒有在那個時間點做特殊操作。
感覺快找到原因了。執(zhí)行以下命令來查看
stop-datetime 指定為讀取 relay log 報錯的時刻 2023-04-04 16:47:16,
start-datetime 指定為讀取 relay log 報錯的時刻 2023-04-04 16:47:30。
發(fā)現(xiàn)并沒有找到 Update_rows 的操作。繼續(xù)把時間往后加一點,經(jīng)過多次嘗試,把時間鎖定在了 2023-04-04 17:00:30~17:00:31。這 1s 內(nèi)能找到 2023-04-04 16:47:16 的操作日志。
日志如下,這個命令會打印 N 多日志,直接把屏幕打滿了??!
難道真的 binlog 對應(yīng)的這條事務(wù)日志太大了嗎???
存疑: 2023-04-04 16:47:16 時刻對數(shù)據(jù)庫中的表做了某個大事務(wù)的操作,造成該事務(wù)對應(yīng)的這條 binlog 日志很大很大。生成的 relay log 也很大,SQL 線程解析 relay log 報錯。
3.6 這是真相嗎?
問了下熟悉這張表的同事,有沒有在這個時刻做什么大事務(wù)操作。
同事看了下代碼,發(fā)現(xiàn)有個批量插入的操作,一次執(zhí)行 400 條,難道是 400 條太多了???這不應(yīng)該是真正的原因,400 條也不多。
不經(jīng)意間問了下這張表的數(shù)據(jù)量有多大,該同事在 4月4號 16:45:25 做了一個手動備份 xx_dance 表的操作,這張表有 25 萬條數(shù)據(jù)。
備份表 xx_dance_0404 的信息
這個備份操作是在一個事務(wù)里面執(zhí)行的,生成的一條 binlog 日志很大。
這里只是一個猜測,還未得到驗證,文末會說明真正的原因。
如果真的是這樣,那我可以先恢復(fù)從庫的同步,備份表的操作在從庫上其實不需要。
3.7 GTID
不知道細(xì)心的你是否有發(fā)現(xiàn)上面的 binlog 里面有一個GTID,
binlog 中 GTID
記住 GTID 中的數(shù)字 8634832,后面恢復(fù)從庫同步時要用到。
我們再來看下從庫的狀態(tài),發(fā)現(xiàn)也有一個 GTID,如下圖所示,值為 8634831,正好相差 1,感覺這兩個 GTID 值之間有不可告人的秘密。
從庫的狀態(tài),GTID 集合
那么從庫 SQL 線程停止運行的原因就是卡在 8634832 這里了,我們可否跳過這個 GTID 呢?這里就需要了解 GTID 的原理了。
你可能對 GTID 的原理很感興趣,可以查看之前悟空寫的一篇文章:
MySQL 主從模式采用 GTID 的實踐
四、GTID 同步方式的原理
這里還是把主從同步采用 GTID 方式的流程拿出來看下,幫助大家快速回顧下,熟悉的同學(xué)可以跳過本節(jié)內(nèi)容。
GTID 方案:主庫計算主庫 GTID 集合和從庫 GTID 的集合的差集,然后主庫推送差集 binlog 給從庫。
當(dāng)從庫設(shè)置完同步參數(shù)后,假定主庫 A 的GTID 集合記為集合 x,從庫 B 的 GTID 集合記為 y。
從庫同步的邏輯如下:
GTID 同步方式的原理
- 從庫 B 指定主庫 A,基于主備協(xié)議簡歷連接。
- 從庫 B 把集合 y 發(fā)給主庫 A。
- 主庫 A 計算出集合 x 和集合 y 的差集,也就是集合 x 中存在,集合 y 中不存在的 GTID 集合。比如集合 x 是 1~100,集合 y 是 1~90,那么這個差集就是 91~100。這里會判斷集合 x 是不是包含有集合 y 的所有 GTID,如果不是則說明主庫 A 刪除了從庫 B 需要的 binlog,主庫 A 直接返回錯誤。
- 主庫 A 從自己的 binlog 文件里面,找到第一個不在集合 y 中的事務(wù) GTID,也就是找到了 91。
- 主庫 A 從 GTID = 91 的事務(wù)開始,往后讀 binlog 文件,按順序取 binlog,然后發(fā)給 B。
- 從庫 B 的 I/O 線程讀取 binlog 文件生成 relay log,SQL 線程解析 relay log,然后執(zhí)行 SQL 語句。
GTID 同步方案和位點同步的方案區(qū)別是:
- 位點同步方案是通過人工在從庫上指定哪個位點,主庫就發(fā)哪個位點,不做日志的完整性判斷。
- 而 GTID 方案是通過主庫來自動計算位點的,不需要人工去設(shè)置位點,對運維人員友好。
五、恢復(fù)從庫的同步
5.1 查看從庫執(zhí)行 GTID 的進(jìn)度
在從庫上執(zhí)行 show slave status \G來查看 GTID 集合。
Retrieved_Gtid_Set 表示從庫收到的所有日志的 GTID 集合。
Executed_Gtid_Set 表示從庫已經(jīng)執(zhí)行完成的 GTID 集合。
如果 Executed_Gtid_Set 集合是包含 Retrieved_Gtid_Set,則表示從庫接收到的日志已經(jīng)同步完成。
這里 Executed_Gtid_Set 的集合為 1-8634831,而 Retrieved_Gtid_Set 為 1-9101426,說明從庫有些 GTID 是沒有執(zhí)行的。從庫已經(jīng)執(zhí)行到了 8634831,下一個要執(zhí)行的 GTID 為 8634832。
因為我們采用的同步方式是 GTID 方式,所以只要讓從庫跳過這個 GTID ,從下一個 GTID 開始同步就行。
帶來的問題就是這個 GTID 對應(yīng)的事務(wù)沒有執(zhí)行。因為報錯的操作是從庫備份一張大表,所以從庫跳過這個備份操作也是可以接受的。
5.2 手動設(shè)置 GTID
來,手動設(shè)置一把 GTID 試下。
5.2.1 重置從庫進(jìn)度
首先重置下從庫同步的進(jìn)度 reset slave,這條命令會把所有的 relog 給清理掉,重新啟用一個新的 relay log文件。
重新開啟同步后,主庫會計算主庫 GTID 集合和從庫 GTID 的集合的差集,然后主庫推送差集 binlog 給從庫。
5.2.1 設(shè)置 GTID 為一個值
執(zhí)行以下命令設(shè)置 GTID 為下一個值。
gtid_next 表示設(shè)置下一個 GTID = 8634832,這個值是在原來的 8634831 加 1。后面的 begin 和 commit 是提交了一個空事務(wù),把這個 GTID 加到從庫的 GTID 集合中。那么從庫的 GTID 集合就變成了
5.2.2 查看當(dāng)前 GTID 集合
我們可以通過 show master status\G 命令來查看從庫的 GTID 集合。下方截圖是執(zhí)行上述命令之前的。GTID集合為 1-8634831。另外 GTID 集合 為 1 和 GTID 集合為 1-4 的可以忽略,因為它們前面的 Master_UUID 不是當(dāng)前主庫的 uuid。
show master status\G 的結(jié)果
也可以通過 show slave status\G 命令來查看 GTID 集合,結(jié)果也是一樣的。
5.3 開啟從庫同步
再次啟動從庫的同步(start slave 命令),I/O 線程和 SQL 線程的狀態(tài)都為 YES,說明啟動成功了。
而且查看從庫的同步狀態(tài)時,觀察到從庫的同步是存在延遲的。通過觀察這個字段 Seconds_Behind_Master 在不斷減小,說明主從同步的延遲越來越小了。
兩個線程都是正常運行,主從同步延遲越來越小
過一段時間后,執(zhí)行的 GTID 等于收到的 GTID 集合,Seconds_Behind_Master = 0,說明主從完全同步了。
六、原因
上面的推測:備份大表造成 binlog 的一條日志太大,relay log 也跟著變大,SQL 線程無法正常解析。
但這是真相嗎?
雖然從庫重新開啟了同步,且跳過了這條日志,但帶來的是從庫上就不會出現(xiàn)這個備用表 xx_dance_0404 。
但出現(xiàn)了兩個奇怪的問題:
問題 1:從庫開啟同步后,居然出現(xiàn)了這個備份表 xx_dance_0404。不是跳過這個備份操作了嗎?目前沒想到原因。
問題 2:為了重現(xiàn)這個問題,我到主庫上做了一個備份表的操作,表名為 xx_dance_0412,從庫也同步了這個新的備份表 xx_dance_0412。而且 binlog 出現(xiàn)的日志現(xiàn)象也是一樣的,對應(yīng)的這條 binlog 日志也很大,但是從庫同步正常。我又備份了一張 300 萬的大表,依然沒重現(xiàn)。
通過問題 2 可以說明上面的推測是錯誤的,備份大表并不會影響主從同步。
那么 relay log 報錯的原因是什么?
只有一個原因了,relay log 文件真的是損壞的,從庫的狀態(tài)上也說明了原因,relay log is corrupted(損壞)。SQL 線程去解析 relay log 時報錯了,導(dǎo)致 SQL 線程停止,從庫不能正常執(zhí)行同步。
小結(jié):relay log 損壞了,導(dǎo)致從庫的 SQL 線程解析 relay log 時出現(xiàn)異常。從庫恢復(fù)方式是通過手動設(shè)置當(dāng)時出錯的 GTID 的下一個值,讓從庫不從主庫同步這個 GTID,最后從庫就能正常同步這個 GTID 之后的 binlog 了,后續(xù) SQL 線程也能正常解析 relay log 了。
關(guān)于我
InfoQ 簽約作者、藍(lán)橋簽約作者、阿里云專家博主、51CTO 紅人。