MySQL從3.23版本開始引入了二進制日志,用於的數據復制, 二進制日志根據MySQL的版本不同,目前有4個版本:
https://dev.mysql.com/doc/internals/en/binlog-version.html
Version 1: supported statement based replication events.
Version 2: can be ignored as it was only used in early alpha versions of MySQL 4.1.x
and won't be documented here.
Version 3: added the relay logs and changed the meaning of the log position.
Version 4: added the FORMAT_DESCRIPTION_EVENT and made the protocol extensible.
二進制日志版本是向后兼容的, 后一個版本可以看成是對前一個版本的繼承和擴展,需要注意的是,version 2是一個臨時版本, 可以忽略。事實上可以只關心v4版本,因為現在跑的MySQL都應該是MySQL 5+了,就如現再討論Oracle 8i, 9i沒什么實際意義。截至當前最新的MySQL 8版本,使用的依然是v4版本。
題外話:
BinlogMiner的解析器為保持代碼穩定,做了接口將功能和具體實現分隔, 但實際上目前也只有一個BinlogParser4實現。也許以后會有V5版本,還有一個問題就是開源軟件的碎片化,目前主流的有3個分支,本人偏愛Percona版本,Percona版本完全兼容官方版本,而在性能和可維護性上有提高,非常討人喜歡。國內一些大的互聯網企業如騰訊,阿里也有做基於MySQL的數據庫,目前還沒有研究,不知道兼容性如何。
MySQL的二進制日志文件以事件為單位進行封裝,文件的結構如下:
說明:二進制日志可以看成是二進制事件的集合,不同的事件,對應於不同的功能,MySQL包含的事件類型可以參考:
https://dev.mysql.com/doc/internals/en/binlog-event-type.html
A start event (START_EVENT_V3) is the first event of a binlog for binlog-version 1 to 3.
A format description event (FORMAT_DESCRIPTION_EVENT) is the first event of a binlog for binlog-version 4.
v1-v3版本, 二進制日志文件的第一個事件是START_EVENT_V3, 而v4版本開始第一個事件是FORMAT_DESCRIPTION_EVENT,替代掉START_EVENT_V3.
二進制日志的結束事件為STOP_EVENT或者ROTATE_EVENT,出現其中之一就說應二進制文件已經結束, 其中STOP_EVENT說應MySQL服務器已經關閉, 而ROTATE_EVENT則說明二進制達到了max_binlog_size的閾值,或者在線修改了binlog-format,導致了二進制文件的切換。
二進制日志QUERY_EVENT和ROWS_EVENT(包括WRITE_ROWS_EVENT/UPDATE_ROWS_EVENT/DELETE_ROWS_EVENT)來記錄數據變化, 所有的DDL,如(create table ...)都是通過QUERY_EVENT記錄的, 而DML(inert/update/delete)則根據復制模式的不同(binlog-format)而不同, 基於語句的復制(Statement-Based),DML語句以語句形式記錄在QUERY_EVENT中,而基於行的復制(Row-Based Replication),則將受到DML語句影響的行的值,記錄在ROWS_EVENT中。顯而易見, 基於語句的復制一個明顯的優勢就是數據量小,delete table xxx,只記錄一個語句就可以了,但是行模式則需要記錄所有行的值。但如前文說的基於語句的復制不是絕對安全的,當遇到"Nondeterministic"的語句,會由問題,比如SYSDATE(),如果將函數復制到備庫執行,得到的結果和主庫肯定不一樣,又如USER()調用的用戶不同,得到的結果也不同。當然可以通過一些選項,在遇到有些函數時轉換成函數的結果復制,但並不是說有的函數都能解決,特別是自定義的函數。基於行的賦值,ROWS_EVENT中還包含修改的“前值”,BinlogMiner就是通過這些“前值”達到閃回的效果。
1. magic number
用於表示二進制日志文件, 4個字節長度, 其值為固定的:0xfe 0x62 0x69 0x6e; 緊接着的是一個個的二進制日志事件。二進制日志的每個事件的結構如下:
2. Common Header
通用文件頭, 其實定義了一個事件的基本信息, 包含事件的起止位置, 類型, 時間搓和服務器ID等信息, 我們依賴這些信息特別起止位置來遍歷整個二進制日志文件,Common Header的結構如下:
https://dev.mysql.com/doc/internals/en/binlog-event-header.html
Binlog header Payload:
4 timestamp
1 event type
4 server-id
4 event-size
if binlog-version > 1:
4 log pos
2 flags
可以看到Common Header的長度是固定的13個字節或者19個字節。只有v1版本是13個字節, 后續的版本都是19個字節, 主要是多了log pos, 也就是當前事件的結束位置, v1版本雖然沒有結束位置, 但是是可以通過事件的開始位置 + 事件長度(event-size)計算出來的,之所以稱為Common Header,是因為這部分是與具體事件無關的。
3. Post-Header
Common Header后緊跟着的是Post-Header部分, Post-Header是跟具體事件相關的,而且並不是每個事件都有Post-Header(可以為0),Post-Header的長度對於一個MySQL版本是固定的,但不同版本可能不同,每種事件的Post-header的長度在FORMAT_DESCRIPTION_EVENT中有記錄。
4. PlayLoad
Post-Header后緊跟着負載(playload), 也就是具體的內容,這部分是不固定長度的,直到事件的結束(也就是Checksum)。
以一個QUERY_EVENT的案例來概覽一下Post-header和playLoad:
* QUERY_EVENT: The query event is used to send text querys right the binlog.
*
* References:
* https://dev.mysql.com/doc/internals/en/query-event.html
* https://dev.mysql.com/doc/internals/en/event-data-for-specific-event-types.html
*
* Post-header :
* 4 slave_proxy_id
* 4 execution time
* 1 schema length
* 2 error-code
* if binlog-version ≥ 4:
* 2 status-vars length
*
* Payload:
* string[$len] status-vars
* string[$len] schema
* 1 [00]
* string[EOF] query
5. Checksum
也就是事件的校驗值, 在MySQL 5.6.2版本開始引入,5.6.6版本開始默認開啟(CRC32), 這部分在事件的結尾處, 目前只支持CRC算法,檢驗值為4個字節,校驗算法在FORMAT_DESCRIPTION_EVENT事件中通過1個字節記錄。
https://dev.mysql.com/doc/refman/5.6/en/replication-options-binary-log.html#option_mysqld_binlog-checksum