mysql同步row模式_ROW模式的SQL无法正常同步的问题总结
ROW模式的SQL無法正常同步的問題總結
最近處理數據庫問題時遇到一起mysql從機ROW模式的SQL無法正常同步的問題,今天剛好有時間,將整個過程總結一下,方便后面的同學學習!
一、問題起因
最近有一個業務的實例在比對數據的時候數據庫、表、以及行數都是一樣的,只是有多個表的checksum值不一致,主從狀態也正常。初步判斷可能是運維之前有做過skip的操作導致,對從機進行了重做后發現問題依舊,于是對binlog的內容進行了分析跟進,來找到不能同步的根本原因。分析binlog的過程如下:
?選擇一條checksum主從不一致的表的一條最近的記錄
從上面兩張圖中可以很清楚的看出兩條記錄的內容不一致,那么為什么會不一致呢?我們來追蹤binlog看看
?通過binlog進行追蹤master上這條記錄的執行情況
在master上找到了對應的update的記錄。下面到從機上找一下對應的relay日志和binlog看看是否有正常復制和執行。
?在slave的relay日志和binlog中都查找對應master上的那條binlog的執行情況
在slave上發現一個奇怪的現象,在relay日志中能找到對應的更新這條語句的SQL,也就是說在master上的binlog已經通過從機的IO線程將對應的update語句同步到了relay日志中。但是在slave的binlog中沒有找到運行這條SQL的記錄,并且從機上的位置早就已經超過了那個update的位置,排除了slave端延遲的問題。
跟蹤多條ROW模式的SQL均是此問題,而STATEMENT的SQL不會出現異常的問題,從機上的所有的binlog顯示都是STATEMENT的SQL。主從binlog的比較也能看出確實有異常(備注:開啟了log-slave-updates參數),如下截圖所示:
Master上的binlog的數量和大小:
Slave上的binlog的數量和大小:
匯總問題如下:
Slave無法同步Master上的ROW模式的SQL
二、排查過程
出現這個奇怪的靈異問題,首先想到的是mysql某個版本的bug,找各個版本的服務器進行重現該問題,發現相同的SQL在其他服務器都沒有此問題。但是當采用其他的各個版本或者相同版本的mysql作為那臺有問題的master的從機,就都出現了不能同步ROW模式的SQL的問題。因此基本可以排除版本的問題。
那么是什么原因在從機上ROW模式的SQL沒有執行呢?什么場景會觸發這種問題呢?
經過開發同學zhiyangli和edgeyang的源碼定位終于找到了問題原因,問題原因是sql線程將relay log保存的64位table id轉換成32位溢出,導致在hash結構中找不到對應的表而不進行任何操作,具體的邏輯為:binlog中table_id是一個ulong類型(無符號長整形),在slave進行重做binlog events之前,會先將這個ulong的table_id(為了避免混淆,用m_table_id表示)傳給一個它內部維護的一個數據結構RPL_TABLE_LIST,這個里面有一個變量table_id用來存儲binlog中的m_table_id,問題出現了:數據結構的變量table_id是一個uint(無符號整形),如果m_table_id超過uint的范圍會發生截斷。而MySQL內部在構造hash,從hash表中取值是這樣的做法:set_table(table_id),get_table(m_table_id),在兩個階段用到的key因為發生了數據截斷所以必然也就不能取到預期的值。也就是說之前用uint型的table_id構建出來的key-value的hash對,用ulong型的m_table_id是無法查詢到的。也就是如果binlog中顯示的tableid超過2的32次方就是42億的時候就會觸發這個bug。通過重啟能臨時解決問題。如下截圖就是有問題的master產生的binlog:
從這個圖中我們可以看出table id已經遠遠超過了42億了,達到了109億。
三、深入分析
既然是由于tableid導致,那么tableid是什么東西,為什么要有tableid這個東西呢,以及為什么STATEMENT模式的就不需要tableid呢?另外為什么tableid為上漲得那么厲害超過42億?帶著這些問題,下面就來慢慢分析和解答:
在引入table id之前我們先來說一下mysql的binlog格式
(一)Mysql的binlog格式
搞mysql的同學都知道,mysql的binlog分為三種格式,一種是STATEMENT格式,一種是ROW格式,最后一種是結合STATEMENT和ROW的MIXED格式。下面比對一下各個格式的優缺點:
1.STATEMENT
a)優點
只記錄執行的SQL語句本身,binlog量少,節省IO,性能比較好
b)缺點
對一些卻確定的函數比如uuid()、limit、user()等不能保證主從數據的一致性。
2.ROW
a)優點
Row格式非常清楚地記錄下每一行數據的修改細節,能保證主從數據的一致性。
b)缺點
Binlog太多,IO性能受限制,另外對從機的主從延遲也是一個挑戰。
3.MIXED
結合了STATEMENT和ROW模式的有點。
(二)Table id是個啥東西
先來看兩個binlog中的SQL語句
STATEMENT格式的binlog:
從mysql的binlog中發現statement的SQL是沒有table id的,從STATEMENT中記錄的SQL,我們可以看出,通過SQL就知道更改表的對應位置,因此不需要通過table id去查找到對應的表的結構信息。
ROW格式的binlog:
從截圖中可以看出ROW模式中含有table id的概念,ROW模式引入table id是為了在執行insert/update/delete解析的時候能夠知道具體的表信息,因為我們通過binlog可以看到,語句并不能反應出列名信息。因此通過table id來關聯表結構信息。從table_map_id代碼中也能看table id就是專門用于ROW格式的:
ulong table_map_id; /*for row-based replication*/
(三)Tablemap和table id
ROW模式的binlog中有如下兩行信息:
Table id就是table map映射key ID,從binlog中可以看到mysql分2個events分別記錄這些信息events 1,記錄了操作哪些庫哪些表。其中會將這些信息緩存到一個hash map內,key為tabke_id,value為table類(保存了庫名,表名等信息),events 2記錄了操作哪些行。每次執行events 2的時候,mysql通過table_id先去hash map查找相關的table信息。找到庫表后再操作具體的行。一個table map events可以對應多個row events,以此減少binlog占用空間。
(四)Table id增長和cache的關系
從代碼中可以看出table id的分配在函數assign_new_table_id(),每次分配都是對上一次的table id自增,代碼如下:
一般是DDL語句會導致table id增加。
下來再看看table id和cache的關系,網上有代碼分析了,table id是保存在cache中,當cache中有該表定義時,表對應的table id是不變的,而當cache中沒有改表定義時,該值根據上一次操作的table id自增1獲得的。Cache指的是table cache,由table_definition_cache組成,這里就會引出一個問題,當table cache過小而表的數量又很多的場景,會導致表定義將被頻繁置換出cache,被置換出的表如果有操作時,重新加載時,table id的值就會發生改變。因此,table id與實際操作的數據表沒有直接對應關系,而與操作的數據表是否在table cache中有關。
總結有如下兩個方面會導致table id增加:
?DDL語句執行的時候。
?Table cache設置太小,表定義被頻繁置換出cache,導致table id增加。
?執行flush tables
(五)為什么table id超過42億同步就有問題呢?
這里涉及到mysql的bug,在定義table id的時候采用的ulong型,為8byte。而在同步的SQL線程中設置的table id為uint型,為4byte,因此同步的SQL線程中如果超過2^32的話就溢出了,主機的update等就無法同步更新到從機。具體代碼如下:
四、問題解決
知道了問題原因就好解決了,主要有如下兩種解決辦法:
1.修改代碼修復uint的問題。
2.重啟實例,并將table cache調大。
五、問題跟進處理
對于一個平臺來講,雖然遇到的機會比較少,但是這種問題側出現反應了我們平臺還是有一些監控的盲點和漏洞,需要對table id進行監控,另外對table cache的默認配置400還是非常小的。因此接下來三個任務:
1.更改線上的版本,修復uint的問題。
2.對于存量的需要將table cache進行一次整體的調整。
3.推動添加table id的監控,防止類似的問題出現。
六、參考資料
總結
以上是生活随笔為你收集整理的mysql同步row模式_ROW模式的SQL无法正常同步的问题总结的全部內容,希望文章能夠幫你解決所遇到的問題。
- 上一篇: mysql x64界面配置版下载_MyS
- 下一篇: python服务器搭建nginx_Ngi