MySQL binlog原來(lái)可以這樣用?各種場(chǎng)景和原理剖析!
作者:田守枝
來(lái)自:田守枝的博客(公眾號(hào))
提醒:喜歡記得右下角關(guān)注一波
本文深入介紹Mysql Binlog的應(yīng)用場(chǎng)景,以及如何與MQ、elasticsearch、redis等組件的保持?jǐn)?shù)據(jù)最終一致。最后通過(guò)案例深入分析binlog中幾乎所有event是如何產(chǎn)生的,作用是什么。
1 基于binlog的主從復(fù)制
Mysql 5.0以后,支持通過(guò)binary log(二進(jìn)制日志)以支持主從復(fù)制。復(fù)制允許將來(lái)自一個(gè)MySQL數(shù)據(jù)庫(kù)服務(wù)器(master) 的數(shù)據(jù)復(fù)制到一個(gè)或多個(gè)其他MySQL數(shù)據(jù)庫(kù)服務(wù)器(slave),以實(shí)現(xiàn)災(zāi)難恢復(fù)、水平擴(kuò)展、統(tǒng)計(jì)分析、遠(yuǎn)程數(shù)據(jù)分發(fā)等功能。
二進(jìn)制日志中存儲(chǔ)的內(nèi)容稱之為事件,每一個(gè)數(shù)據(jù)庫(kù)更新操作(Insert、Update、Delete,不包括Select)等都對(duì)應(yīng)一個(gè)事件。
注意:本文不是講解mysql主從復(fù)制,而是講解binlog的應(yīng)用場(chǎng)景,binlog中包含哪些類型的event,這些event的作用是什么。你可以理解為,是對(duì)主從復(fù)制中關(guān)于binlog解析的細(xì)節(jié)進(jìn)行深度剖析。而講解主從復(fù)制主要是為了理解binlog的工作流程。
下面以mysql主從復(fù)制為例,講解一個(gè)從庫(kù)是如何從主庫(kù)拉取binlog,并回放其中的event的完整流程。mysql主從復(fù)制的流程如下圖所示:

主要分為3個(gè)步驟:
第一步:master在每次準(zhǔn)備提交事務(wù)完成數(shù)據(jù)更新前,將改變記錄到二進(jìn)制日志(binary log)中(這些記錄叫做二進(jìn)制日志事件,binary log event,簡(jiǎn)稱event)
第二步:slave啟動(dòng)一個(gè)I/O線程來(lái)讀取主庫(kù)上binary log中的事件,并記錄到slave自己的中繼日志(relay log)中。?
第三步:slave還會(huì)起動(dòng)一個(gè)SQL線程,該線程從relay log中讀取事件并在備庫(kù)執(zhí)行,從而實(shí)現(xiàn)備庫(kù)數(shù)據(jù)的更新。
2 binlog的應(yīng)用場(chǎng)景
binlog本身就像一個(gè)螺絲刀,它能發(fā)揮什么樣的作用,完全取決你怎么使用。就像你可以使用螺絲刀來(lái)修電器,也可以用其來(lái)固定家具。
2.1 讀寫分離
最典型的場(chǎng)景就是通過(guò)Mysql主從之間通過(guò)binlog復(fù)制來(lái)實(shí)現(xiàn)橫向擴(kuò)展,來(lái)實(shí)現(xiàn)讀寫分離。如下圖所示:

?? 在這種場(chǎng)景下:
有一個(gè)主庫(kù)Master,所有的更新操作都在master上進(jìn)行
同時(shí)會(huì)有多個(gè)Slave,每個(gè)Slave都連接到Master上,獲取binlog在本地回放,實(shí)現(xiàn)數(shù)據(jù)復(fù)制。
在應(yīng)用層面,需要對(duì)執(zhí)行的sql進(jìn)行判斷。所有的更新操作都通過(guò)Master(Insert、Update、Delete等),而查詢操作(Select等)都在Slave上進(jìn)行。由于存在多個(gè)slave,所以我們可以在slave之間做負(fù)載均衡。通常業(yè)務(wù)都會(huì)借助一些數(shù)據(jù)庫(kù)中間件,如tddl、sharding-jdbc等來(lái)完成讀寫分離功能。
因?yàn)楣ぷ餍再|(zhì)的原因,筆者見(jiàn)過(guò)最多的一個(gè)業(yè)務(wù),一個(gè)master,后面掛了20多個(gè)slave。筆者之前寫過(guò)一篇關(guān)于數(shù)據(jù)庫(kù)中間件實(shí)現(xiàn)原理的文章,感興趣的讀者可以參考:數(shù)據(jù)庫(kù)中間件詳解
2.2 數(shù)據(jù)恢復(fù)
一些同學(xué)可能有誤刪除數(shù)據(jù)庫(kù)記錄的經(jīng)歷,或者因?yàn)檎`操作導(dǎo)致數(shù)據(jù)庫(kù)存在大量臟數(shù)據(jù)的情況。例如筆者,曾經(jīng)因?yàn)檎`操作污染了業(yè)務(wù)方幾十萬(wàn)數(shù)據(jù)記錄。
如何將臟數(shù)據(jù)恢復(fù)成原來(lái)的樣子?如果恢復(fù)已經(jīng)被刪除的記錄?
這些都可以通過(guò)反解binlog來(lái)完成,筆者也是通過(guò)這個(gè)手段,來(lái)恢復(fù)業(yè)務(wù)方的記錄。
2.3 數(shù)據(jù)最終一致性
在實(shí)際開發(fā)中,我們經(jīng)常會(huì)遇到一些需求,在數(shù)據(jù)庫(kù)操作成功后,需要進(jìn)行一些其他操作,如:發(fā)送一條消息到MQ中、更新緩存或者更新搜索引擎中的索引等。
如何保證數(shù)據(jù)庫(kù)操作與這些行為的一致性,就成為一個(gè)難題。以數(shù)據(jù)庫(kù)與redis緩存的一致性為例:操作數(shù)據(jù)庫(kù)成功了,可能會(huì)更新redis失??;反之亦然。很難保證二者的完全一致。
遇到這種看似無(wú)解的問(wèn)題,最好的辦法是換一種思路去解決它:不要同時(shí)去更新數(shù)據(jù)庫(kù)和其他組件,只是簡(jiǎn)單的更新數(shù)據(jù)庫(kù)即可。
如果數(shù)據(jù)庫(kù)操作成功,必然會(huì)產(chǎn)生binlog。之后,我們通過(guò)一個(gè)組件,來(lái)模擬的mysql的slave,拉取并解析binlog中的信息。通過(guò)解析binlog的信息,去異步的更新緩存、索引或者發(fā)送MQ消息,保證數(shù)據(jù)庫(kù)與其他組件中數(shù)據(jù)的最終一致。
在這里,我們將模擬slave的組件,統(tǒng)一稱之為binlog同步組件。你并不需要自己編寫這樣的一個(gè)組件,已經(jīng)有很多開源的實(shí)現(xiàn),例如linkedin的databus,阿里巴巴的canal,美團(tuán)點(diǎn)評(píng)的puma等。
當(dāng)我們通過(guò)binlog同步組件完成數(shù)據(jù)一致性時(shí),此時(shí)架構(gòu)可能如下圖所示:

增量索引
通常索引分為全量索引和增量索引。對(duì)于增量索引的部分,可以通過(guò)監(jiān)聽binlog變化,根據(jù)binlog中包含的信息,轉(zhuǎn)換成es語(yǔ)法,進(jìn)行實(shí)時(shí)索引更新。當(dāng)然,你可能并沒(méi)有使用es,而是solr,這里只是以es舉例。
可靠消息
可靠消息是指的是:保證本地事務(wù)與發(fā)送消息到MQ行為的一致性。一些業(yè)務(wù)使用本地事務(wù)表或者獨(dú)立消息服務(wù),來(lái)保證二者的最終一致。Apache RocketMQ在4.3版本開源了事務(wù)消息,也是用于完成此功能。事實(shí)上,這兩種方案,都有一定侵入性,對(duì)業(yè)務(wù)不透明。通過(guò)訂閱binlog來(lái)發(fā)送可靠消息,則是一種解耦、無(wú)侵入的方案。關(guān)于可靠消息,筆者最近寫了一篇文章, 感興趣的讀者可以參考:可靠消息一致性的奇淫技巧。
緩存一致性
業(yè)務(wù)經(jīng)常遇到的一個(gè)問(wèn)題是,如何保證數(shù)據(jù)庫(kù)中記錄和緩存中數(shù)據(jù)的一致性。不妨換一種思路,只更新數(shù)據(jù)庫(kù),數(shù)據(jù)庫(kù)更新成功后,通過(guò)拉取binlog來(lái)異步的更新緩存(通常是刪除,讓業(yè)務(wù)回源到數(shù)據(jù)庫(kù))。如果數(shù)據(jù)庫(kù)更新失敗,沒(méi)有對(duì)應(yīng)binlog,那么也不會(huì)去更新緩存,從而實(shí)現(xiàn)最終一致性。
可以看到,binlog是一把利器,可以保證數(shù)據(jù)庫(kù)與與其他任何組件(es、mq、redis等)的最終一致。這是一種優(yōu)雅的、通用的、無(wú)業(yè)務(wù)入侵的、徹底的解決方案。我們沒(méi)有必要再單獨(dú)的研究某一種其他組件如何與數(shù)據(jù)庫(kù)保持最終一致,可以通過(guò)binlog來(lái)實(shí)現(xiàn)統(tǒng)一的解決方案。
在實(shí)際開發(fā)中,你可以簡(jiǎn)單的像上圖那樣,每個(gè)應(yīng)用場(chǎng)景都模擬一個(gè)slave,各自連接到Mysql上去拉取binlog,master會(huì)給每個(gè)連接上來(lái)的slave一份完整的binlog拷貝,業(yè)務(wù)拿到各自的binlog之后進(jìn)行消費(fèi),彼此之間互不影響。但是這樣,有一些弊端,多個(gè)slave會(huì)給master帶來(lái)一些額外管理上的開銷,網(wǎng)卡流量也將翻倍的增長(zhǎng)。
我們可以進(jìn)行一些優(yōu)化,之所以不同場(chǎng)景模擬多個(gè)slave來(lái)連接master獲取同一份binlog,本質(zhì)上要滿足的是:一份binlog數(shù)據(jù),同時(shí)提供給多個(gè)不同業(yè)務(wù)場(chǎng)景使用,彼此之間互不影響。
顯然,消息中間件是一個(gè)很好的解決方案。現(xiàn)在很多主流的消息中間件,都支持consumer group的概念,如kafka、rocketmq等。同一個(gè)topic中的數(shù)據(jù),可以由多個(gè)不同consumer group來(lái)消費(fèi),且不同的consumer group之間是相互隔離的,例如:當(dāng)前消費(fèi)到的位置(offset)。
因此,我們完全可以將binlog,統(tǒng)一都發(fā)送到MQ中,不同的應(yīng)用場(chǎng)景使用不同的consumer group來(lái)消費(fèi),彼此之間互不影響。此時(shí)架構(gòu)如下圖所示:

通過(guò)這樣方式,我們巧妙的達(dá)到了一份數(shù)據(jù)多個(gè)應(yīng)用場(chǎng)景來(lái)使用。一般,一個(gè)Mysql實(shí)例中可能會(huì)創(chuàng)建多個(gè)庫(kù)(Database),通常我們會(huì)將一個(gè)庫(kù)的binlog放到一個(gè)對(duì)應(yīng)的MQ中的Topic中。
當(dāng)將binlog發(fā)送到MQ中后,我們就可以利用MQ的一些高級(jí)特性了。例如binlog發(fā)送到MQ過(guò)快,消費(fèi)方來(lái)不及消費(fèi),可以利用MQ的消息堆積能力進(jìn)行流量削峰。還可以利用MQ的消息回溯功能,例如一個(gè)業(yè)務(wù)需要消費(fèi)歷史的binlog,此時(shí)MQ中如果還有保存,那么就可以直接進(jìn)行回溯。
當(dāng)然,有一些binlog同步組件可能實(shí)現(xiàn)了類似于MQ的功能,此時(shí)你就無(wú)序再單獨(dú)的使用MQ。
2.4 異地多活
一個(gè)更大的應(yīng)用場(chǎng)景,異地多活場(chǎng)景下,跨數(shù)據(jù)中心之間的數(shù)據(jù)同步。這種場(chǎng)景的下,多個(gè)數(shù)據(jù)中心都需要寫入數(shù)據(jù),并且往對(duì)方同步。以下是一個(gè)簡(jiǎn)化的示意圖:

這里有一些特殊的問(wèn)題需要處理。典型的包括:
數(shù)據(jù)沖突:雙方同時(shí)插入了一個(gè)相同主鍵的值,那么往對(duì)方同步時(shí),就會(huì)出現(xiàn)主鍵沖突的錯(cuò)誤。
數(shù)據(jù)回環(huán):一個(gè)庫(kù)A中插入的數(shù)據(jù),通過(guò)binlog同步到另外一個(gè)庫(kù)B中,依然會(huì)產(chǎn)生binlog。此時(shí)庫(kù)B的數(shù)據(jù)再次同步回庫(kù)A,如此反復(fù),就形成了一個(gè)死循環(huán)。
如何解決數(shù)據(jù)沖突、數(shù)據(jù)回環(huán),就變成了binlog同步組件要解決的問(wèn)題。同樣,業(yè)界也有了成熟的實(shí)現(xiàn),比較知名的有阿里開源的otter,以及摩拜(已經(jīng)屬于美團(tuán))的DRC等。
筆者之前寫過(guò)一篇文章,介紹如何在多機(jī)房進(jìn)行數(shù)據(jù)同步,感興趣的讀者可以參考以下文章:異地多活場(chǎng)景下的數(shù)據(jù)同步之道
2.5 小結(jié)
如前所屬,binlog的作用如此強(qiáng)大。因此,你可能想知道binlog文件中到底包含了哪些內(nèi)容,為什么具有如此的魔力?在進(jìn)行一些數(shù)據(jù)庫(kù)操作時(shí),例如:Insert、Update、Delete等,到底會(huì)對(duì)binlog產(chǎn)生什么樣的影響?這正是本文要下來(lái)要講解的內(nèi)容。
3 Binlog事件詳解
Mysql已經(jīng)經(jīng)歷了多個(gè)版本的發(fā)布,最新已經(jīng)到8.x,然而目前企業(yè)中主流使用的還是Mysql 5.6或5.7。不同版本的Mysql中,binlog的格式和事件類型可能會(huì)有些細(xì)微的變化,不過(guò)暫時(shí)我們并不討論這些細(xì)節(jié)。
總的來(lái)說(shuō),binlog文件中存儲(chǔ)的內(nèi)容稱之為二進(jìn)制事件,簡(jiǎn)稱事件。我們的每一個(gè)數(shù)據(jù)庫(kù)更新操作(Insert、Update、Delete等),都會(huì)對(duì)應(yīng)的一個(gè)事件。
從大的方面來(lái)說(shuō),binlog主要分為2種格式:
Statement模式:binlog中記錄的就是我們執(zhí)行的SQL;
Row模式:binlog記錄的是每一行記錄的每個(gè)字段變化前后得到值。
熟悉主從復(fù)制的同學(xué),應(yīng)該知道,還有第三種模式Mixed(即混合模式),從嚴(yán)格意義上來(lái)說(shuō),這并不是一種新的binlog格式,只是結(jié)合了Statement和Row兩種模式而已。
當(dāng)我們選擇不同的binlog模式時(shí),在binlog文件包含的事件類型也不相同,如:?1)在Statement模式下,我們就看不到Row模式下獨(dú)有的事件類型。2)有一些類型的event,必須在我們開啟某些特定配置的情況下,才會(huì)出現(xiàn);3)當(dāng)然也會(huì)有一些公共的event類型,在任何模式下都會(huì)出現(xiàn)。
Mysql中定義了30多個(gè)event類型,這里并不打算將所有的事件類型提前列出,這樣沒(méi)有意義,只會(huì)讓讀者茫然不知所措。筆者將會(huì)在必要的地方,介紹遇到的每一種event類型的作用。
目前我們先從宏觀的角度對(duì)binlog有一個(gè)感性的認(rèn)知。
3.1?多文件存儲(chǔ)
mysql 將數(shù)據(jù)庫(kù)更新操作對(duì)應(yīng)的event記錄到本地的binlog文件中,顯然在一個(gè)文件中記錄所有的event是不可能的,過(guò)大的文件會(huì)給我們的運(yùn)維帶來(lái)麻煩,如刪除一個(gè)大文件,在I/O調(diào)度方面會(huì)給我們帶來(lái)不可忽視的資源開銷。
因此,目前基本上所有支持本地文件存儲(chǔ)的組件,如MQ、Mysql等,都會(huì)控制一個(gè)文件的大小。在數(shù)據(jù)量較多的情況下,就分配到多個(gè)文件進(jìn)行存儲(chǔ)。
在mysql中,我們可以通過(guò)"show binary logs"語(yǔ)句,來(lái)查看當(dāng)前有多少個(gè)binlog文件,以及每個(gè)binlog文件的大小,如下:

另外,mysql提供了:
max_binlog_size配置項(xiàng),用于控制一個(gè)binlog文件的大小,默認(rèn)是1G
expire_logs_days配置項(xiàng),可以控制binlog文件保留天數(shù),默認(rèn)是0,也就是永久保留。
在實(shí)際生產(chǎn)環(huán)境中,一般無(wú)法保留所有的歷史binlog。因?yàn)橐粭l記錄可能會(huì)變更多次,記錄依然是一條,但是對(duì)應(yīng)的binlog事件就會(huì)有多個(gè)。在數(shù)據(jù)變更比較頻繁的情況下,就會(huì)產(chǎn)生大量的binlog文件。此時(shí),則無(wú)法保留所有的歷史binlog文件。
在mysql的percona分支上,還提供了max_binlog_files配置項(xiàng),用于設(shè)置可以保留的binlog文件數(shù)量,以便我們更精確的控制binlog文件占用的磁盤空間。這是一個(gè)非常有用的配置,筆者曾經(jīng)遇到一個(gè)庫(kù),大約10分鐘就會(huì)產(chǎn)生一個(gè)binlog文件,也就是1G,按照這種增長(zhǎng)速度,1天下來(lái)產(chǎn)生的binlog文件,就會(huì)占用大概144G左右的空間,磁盤空間可能很快就會(huì)被使用完。通過(guò)此配置,我們可以顯示的控制binlog文件的數(shù)量,例如指定50,binlog文件最多只會(huì)占用50G左右的磁盤空間。
在更高版本的mysql中,支持按照秒級(jí)精度,來(lái)控制binlog文件的保留時(shí)間。下面我們將對(duì)binlog文件中的內(nèi)容進(jìn)行詳細(xì)的講解。
3.2 Binlog管理事件
所謂binlog管理事件,官方稱之為binlog managent events,你可以認(rèn)為是一些在任何模式下都有可能會(huì)出現(xiàn)的事件,不管你的配置binlog_format是Row、Statement還是Mixed。
以下通過(guò)"show binlog events"語(yǔ)法進(jìn)行查看一個(gè)空的binlog文件,也就是只包含(部分)管理事件,沒(méi)有其他數(shù)據(jù)更新操作對(duì)應(yīng)的事件。如下:

在當(dāng)前binlog v4版本中,每個(gè)binlog文件總是以Format Description Event作為開始,以Rotate Event結(jié)束作為結(jié)束。如果你使用的是很古老的Mysql版本中,開始事件也有可能是START EVENT V3,而結(jié)束事件是Stop Event。在開始和結(jié)束之間,穿插著其他各種事件。
在Event_Type列中,我們看到了三個(gè)事件類型:
Format_desc:也就是我們所說(shuō)的Format Description Event,是binlog文件的第一個(gè)事件。在Info列,我們可以看到,其標(biāo)明了Mysql Server的版本是5.7.10,Binlog版本是4。
Previous_gtids:該事件完整名稱為,PREVIOUS_GTIDS_LOG_EVENT。熟悉Mysql 基于GTID復(fù)制的同學(xué)應(yīng)該知道,這是表示之前的binlog文件中,已經(jīng)執(zhí)行過(guò)的GTID。需要我們開啟GTID選項(xiàng),這個(gè)事件才會(huì)有值,在后文中,將會(huì)詳細(xì)的進(jìn)行介紹。
Rotate:Rotate Event是每個(gè)binlog文件的結(jié)束事件。在Info列中,我們看到了其指定了下一個(gè)binlog文件的名稱是mysql-bin.000004。
關(guān)于"show binlog events"語(yǔ)法顯示的每一列的作用說(shuō)明如下:
Log_name:當(dāng)前事件所在的binlog文件名稱
Pos:當(dāng)前事件的開始位置,每個(gè)事件都占用固定的字節(jié)大小,結(jié)束位置(End_log_position)減去Pos,就是這個(gè)事件占用的字節(jié)數(shù)。細(xì)心的讀者可以看到了,第一個(gè)事件位置并不是從0開始,而是從4。Mysql通過(guò)文件中的前4個(gè)字節(jié),來(lái)判斷這是不是一個(gè)binlog文件。這種方式很常見(jiàn),很多格式的文件,如pdf、doc、jpg等,都會(huì)通常前幾個(gè)特定字符判斷是否是合法文件。
Event_type:表示事件的類型
Server_id:表示產(chǎn)生這個(gè)事件的mysql server_id,通過(guò)設(shè)置my.cnf中的server-id選項(xiàng)進(jìn)行配置。
End_log_position:下一個(gè)事件的開始位置
Info:當(dāng)前事件的描述信息
3.3 Statement模式下的事件
mysql5.0及之前的版本只支持基于語(yǔ)句的復(fù)制,也稱之為邏輯復(fù)制,也就是binary log文件中,直接記錄的就是數(shù)據(jù)更新對(duì)應(yīng)的sql。
假設(shè)有名為test庫(kù)中有一張user表,如下:

現(xiàn)在,我們往user表中插入一條數(shù)據(jù)
insert into user(name) values("tianbowen");之后,可以使用"show binlog events" 語(yǔ)法查看binary log中的內(nèi)容,如下:

?????? 紅色框架中Event,是我們執(zhí)行上面Insert語(yǔ)句產(chǎn)生的4個(gè)Event。下面進(jìn)行詳細(xì)的說(shuō)明:
(劃重點(diǎn))首先,需要說(shuō)明的是,每個(gè)事務(wù)都是以Query Event作為開始,其INFO列內(nèi)容為"BEGIN",以Xid Event表示結(jié)束,其INFO列內(nèi)容為COMMIT。即使對(duì)于單條更新SQL我們沒(méi)有開啟事務(wù),Mysql也會(huì)默認(rèn)的幫我們開啟事務(wù)。因此在上面的紅色框中,盡管我們只是執(zhí)行了一個(gè)INSERT語(yǔ)句,沒(méi)有開啟事務(wù),但是Mysql 默認(rèn)幫我們開啟了事務(wù),所以第一個(gè)Event是Query Event,最后一個(gè)是Xid Event。
接著,是一個(gè)Intvar Event,因?yàn)槲覀兊腎nsert語(yǔ)句插入的表中,主鍵是自增的(AUTO_INCREMENT)列,Mysql首先會(huì)自增一個(gè)值,這就是Intvar Event的作用,這里我們看到INFO列的值為INSERT_ID=1,也就是說(shuō),這次的自增主鍵id為1。需要注意的是,這個(gè)事件,只會(huì)在Statement模式下出現(xiàn)。
然后,還是一個(gè)Query Event,這里記錄的就是我們插入的SQL。這也體現(xiàn)了Statement模式的作用,就是記錄我們執(zhí)行的SQL。
Statement模式下還有一些不常用的Event,如USER_VAR_EVENT,這是用于記錄用戶設(shè)置的變量,僅僅在Statement模式起作用。如:
執(zhí)行以下SQL:
set?@name?=?'tianshouzhi';insert into user(name) values(@name);
這里,我們插入sql的時(shí)候,通過(guò)引用一個(gè)變量。此時(shí)查看binlog變化,這里為了易于觀察,在執(zhí)行show binlog events時(shí),指定了binlog文件和from的位置,即只查看指定binlog文件中從指定位置開始的event。如下:

可以看到,依然符合我們所說(shuō)的,對(duì)于這個(gè)插入語(yǔ)句,依然默認(rèn)開啟了事務(wù)。主鍵自曾值INSERT_ID=2。
當(dāng)然,我們也看到了User var這個(gè)事件,其記錄了我們的設(shè)置的變量值,只不過(guò)以16進(jìn)制顯示。
3.4 Row模式下的事件
mysql5.1開始支持基于行的復(fù)制,這種方式記錄的某條sql影響的所有行記錄變更前和變更后的值。Row模式下主要有以下10個(gè)事件:

很直觀的,我們看到了INSERT、DELETE、UPDATE操作都有3個(gè)版本(v0、v1、v2),v0和v1已經(jīng)過(guò)時(shí),我們只需要關(guān)注V2版本。
此外,還有一個(gè)TABLE_MAP_EVENT,這個(gè)event我們需要特別關(guān)注,可以理解其作用就是記錄了INSERT、DELETE、UPDATE操作的表結(jié)構(gòu)。
下面,我們通過(guò)案例演示,ROW模式是如何記錄變更前后記錄的值,而不是記錄SQL。這里只演示UPDATE,INSERT和DELETE也是類似。
在前面的操作步驟中,我們已經(jīng)插入了2條記錄,如下:

現(xiàn)在需要從Statement模式切換到Row模式,重啟Mysql之后,執(zhí)行以下SQL更新這兩條記錄:
update user set name='wangxiaoxiao';在binary log中,會(huì)把這2條記錄變更前后的值都記錄下來(lái),以下是一個(gè)邏輯示意圖:

該邏輯示意圖顯示了,在默認(rèn)情況下,受到影響的記錄行,每個(gè)字段變更前的和變更后的值,都會(huì)被記錄下來(lái),即使這個(gè)字段的值沒(méi)有發(fā)生變化。
接著,我們還是通過(guò)"show binlog events"語(yǔ)法來(lái)驗(yàn)證:

首先我們可以看到的是,在Row模式下,單條SQL依然會(huì)默認(rèn)開啟事務(wù),通過(guò)Query Event(值為BEGIN)開始,以Xid Event結(jié)束。
接著,我們看到了一個(gè)Table_map 事件,就是前面提到的TABLE_MAP_EVENT,在INFO列,我們可以看到其記錄table_id為108,操作的是test庫(kù)中user表。
最后,是一個(gè)Update_rows事件,然而其INFO,并沒(méi)有像Statement模式那樣,顯示一條SQL,我們無(wú)法直接看到其變更前后的值是什么。
由于存儲(chǔ)的都是二進(jìn)制內(nèi)容,直接vim無(wú)法查看,我們需要借助另外一個(gè)工具mysqlbinlog來(lái)查看其內(nèi)容。如下:

截圖中顯示了2個(gè)event,第一個(gè)紅色框就是Table_map事件,第二個(gè)是Update_rows事件。
在第二個(gè)紅色框架中,顯示了兩個(gè)Update sql,這是只是mysqlbinlog工具為了方便我們查看,反解成SQL而已。我們看到了WHERE以及SET子句中,并沒(méi)有直接列出字段名,而是以@1、@2這樣的表示字段位于數(shù)據(jù)庫(kù)表中的順序。事實(shí)上,這里顯示的內(nèi)容,WHERE部分就是每個(gè)字段修改前的值,而SET部分,則是每個(gè)字段修改后的值,也就是變更前后的值都會(huì)記錄。
這里我們思考以下mysqlbinlog工具的工作原理,其可以將二進(jìn)制數(shù)據(jù)反解成SQL進(jìn)行展示。那么,如果我們可以自己解析binlog,就可以做數(shù)據(jù)恢復(fù),這并非是什么難事。例如用戶誤刪除的數(shù)據(jù),執(zhí)行的是DETELE語(yǔ)句,由于Row模式下會(huì)記錄變更之前的字段的值,我們可以將其反解成一個(gè)INSERT語(yǔ)句,重新插入,從而實(shí)現(xiàn)數(shù)據(jù)恢復(fù)。
3.4.1 binlog_row_image參數(shù)
我們經(jīng)常會(huì)看到一些Row模式和Statement模式的比較。ROW模式下,即使我們只更新了一條記錄的其中某個(gè)字段,也會(huì)記錄每個(gè)字段變更前后的值,binlog日志就會(huì)變大,帶來(lái)磁盤IO上的開銷,以及網(wǎng)絡(luò)開銷。
事實(shí)上,這個(gè)行為可以通過(guò)binlog_row_image控制其有3個(gè)值,默認(rèn)為FULL:?
FULL : 記錄列的所有修改,即使字段沒(méi)有發(fā)生變更也會(huì)記錄。?
MINIMAL :只記錄修改的列。?
NOBLOB :如果是text類型或clob字段,不記錄這些日志。?

我們可以將其修改為MINIMAL,則可以只記錄修改的列的值。
3.4.2 binlog_rows_query_log_events參數(shù)
在Statement模式下,直接記錄SQL比較直觀,事實(shí)上,在Row模式下,也可以記錄。mysql提供了一個(gè)binlog_rows_query_log_events參數(shù),默認(rèn)為值為FALSE,如果為true的情況下,會(huì)通過(guò)Rows Query Event來(lái)記錄SQL。

?可以在my.cnf中添加以下配置,來(lái)開啟row模式下的原始sql記錄(需要重啟):
binlog-rows-query-log_events=1之后,再插入數(shù)據(jù)數(shù)據(jù)時(shí)
insert into user(name) values("maoxinyi");在binlog文件中,我們將看到Rows Query Event

3.5 GTID相關(guān)事件
從MySQL 5.6開始支持GTID復(fù)制。要開啟GTID,修改my.cnf文件,添加以下配置
gtid-mode=onenforce-gtid-consistency=true
在這種情況下,每當(dāng)我們執(zhí)行一個(gè)事務(wù)之前,都會(huì)記錄一個(gè)GTID Event
insert into user("name") values("zhuyihan");此時(shí)binlog內(nèi)容如下:

而當(dāng)我們切換到下一個(gè)binlog文件時(shí),會(huì)記錄之前的已經(jīng)執(zhí)行過(guò)的GTID。這里我們通過(guò)執(zhí)行以下sql手工切換到一個(gè)新的binlog文件。
mysql>?flush?logs;Query OK, 0 rows affected (0.00 sec)
之后在新的binlog文件中,我們看到之前執(zhí)行過(guò)的GTID在下一個(gè)文件中出現(xiàn)了。

本文不是專門講解GTID的文章,感興趣的讀者,可以自行查看相關(guān)資料。
4 總結(jié)
本文對(duì)mysql binlog的應(yīng)用場(chǎng)景進(jìn)行了深入的講解,并介紹了mysql中大部分binlog event的作用。
如果讀者想更加深入的去學(xué)習(xí),例如如何模擬mysql的slave去解析binlog,可以參考一些開源的實(shí)現(xiàn),不過(guò)這些生產(chǎn)級(jí)別的組件,因此通常代碼比較復(fù)雜。筆者自己也造過(guò)類似的輪子,僅僅模擬slave去拉取mysql的binlog,并對(duì)事件進(jìn)行解析,對(duì)于理解binlog解析的核心原理應(yīng)該有一些幫助。
