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