MySQL 核心模組揭秘 | 06 期 | 事務提交之前,binlog 寫到哪裡?

發表於2024-02-28

1. 準備工作

引數配置:

binlog_format = ROW
binlog_rows_query_log_events = OFF

建立測試表:

CREATE TABLE `t_binlog` (
  `id` int unsigned NOT NULL AUTO_INCREMENT,
  `i1` int DEFAULT '0',
  `str1` varchar(32) DEFAULT '',
  PRIMARY KEY (`id`) USING BTREE
) ENGINE=InnoDB DEFAULT CHARSET=utf8mb3;

示例 SQL:

BEGIN;

INSERT INTO `t_binlog` (`i1`, `str1`)
VALUES (100, 'MySQL 核心模組揭秘');

COMMIT;

2. 解析 binlog

執行示例 SQL 之後,我們可以用下面的命令解析事務產生的 binlog 日誌:

cd <binlog 日誌檔案目錄>

mysqlbinlog binlog.000395 \
  --base64-output=decode-rows -vv

解析 binlog 日誌之後,我們可以得到 4 個 binlog event。

按照這些 binlog event 在 binlog 日誌檔案中的順序,簡化之後的內容如下:

  • Query_log_event
# at 1233
# Query    thread_id=8
BEGIN
  • Table_map_log_event
# at 1308
Table_map: `test`.`t_binlog` mapped to number 95
  • Write_rows_log_event
# at 1369
Write_rows: table id 95 flags: STMT_END_F
INSERT INTO `test`.`t_binlog`
SET
  @1=1 /* INT meta=0 nullable=0 is_null=0 */
  @2=100 /* INT meta=0 nullable=1 is_null=0 */
  @3='MySQL 核心模組揭秘' /* VARSTRING(96) meta=96 nullable=1 is_null=0 */
  • Xid_log_event
# at 1438
Xid = 32
COMMIT/*!*/;

示例 SQL 中,只有兩條 SQL 會產生 binlog event:

  • BEGIN:不會產生 binlog event。
  • INSERT:產生三個 binlog event。

    • Query_log_event。
    • Table_map_log_event。
    • Write_rows_log_event。
  • COMMIT:產生 Xid_log_event。

3. binlog cache

我們使用 mysqlbinlog 分析 binlog 日誌的時候,可以發現這麼一個現象:同一個事務產生的 binlog event,在 binlog 日誌檔案中是連續的。

保證同一個事務的 binlog event 在 binlog 日誌檔案中的連續性,不管是 MySQL 從庫回放 binlog,還是作為使用者的我們,都可以很方便的定位到一個事務的 binlog 從哪裡開始,到哪裡結束。

一個事務會產生多個 binlog event,很多個事務同時執行,怎麼保證同一個事務產生的 binlog event 寫入到 binlog 日誌檔案中是連續的?

這就是 cache 發揮用武之地的時候了,每個事務都有兩個 binlog cache:

  • stmt_cache:改變(插入、更新、刪除)不支援事務的表,產生的 binlog event,臨時存放在這裡。
  • trx_cache:改變(插入、更新、刪除)支援事務的表,產生的 binlog event,臨時存放在這裡。

因為我們只介紹 InnoDB 儲存引擎,後面會忽略 stmt_cache,直接介紹 trx_cache。

事務執行過程中,產生的所有 binlog event,都會先寫入 trx_cache。trx_cache 分為兩級:

  • 第一級:記憶體,也稱為 buffer,它的大小用 buffer_length 表示,由系統變數 binlog_cache_size 控制,預設為 32K。
  • 第二級:臨時檔案,位於作業系統的 tmp 目錄下,檔名以 ML 開頭。

buffer_length 加上臨時檔案中已經寫入的 binlog 佔用的位元組數,也有一個上限,由系統變數 max_binlog_cache_size 控制。

4. 產生 binlog

如果一條 SQL 語句改變了(插入、更新、刪除)表中的資料,server 層會為這條 SQL 語句產生一個包含表名和表 ID 的 Table_map_log_event

每次呼叫儲存引擎的方法寫入一條記錄到表中之後,server 層都會為這條記錄產生 binlog。

這裡沒有寫成 binlog event,是因為記錄中各欄位內容都很少的時候,多條記錄可以共享同一個 binlog event ,並不需要為每條記錄都產生一個新的 binlog event。

多條記錄產生的 binlog 共享同一個 binlog event 時,這個 binlog event 最多可以存放多少位元組的內容,由系統變數 binlog_row_event_max_size 控制,預設為 8192 位元組。

如果一條記錄產生的 binlog 超過了 8192 位元組,它的 binlog 會獨享一個 binlog event,這個 binlog event 的大小就不受系統變數 binlog_row_event_max_size 控制了。

在 binlog 日誌檔案中,Table_map_log_event 位於 SQL 語句改變表中資料產生的 binlog event 之前。

示例 SQL 對應的事務中,INSERT 是改變表中資料的第一條 SQL 語句,它插入第一條(也是唯一一條)記錄到 t_binlog 表之後,server 層會為這條記錄產生 binlog event。

插入記錄對應的 binlog event 是 Write_rows_log_event

產生 Write_rows_log_event 之前,server 層會先為 INSERT 構造一個 Table_map_log_event

構造 Table_map_log_event 之前,server 層發現一個問題:示例 SQL 對應的事務,還沒有初始化 binlog cache。

那麼,第一步就要為這個事務初始化 binlog cache,包括 stmt_cache 和 trx_cache。初始化完成之後,這兩個 cache 都是空的。

在 binlog 日誌檔案中,一個事務以內容為 BEGIN 的 Query_log_event 開始。

剛剛初始化完成的 trx_cache 是空的,寫入其它 binlog event 之前,要先寫入一個內容為 BEGIN 的 Query_log_event。

寫入 Query_log_event 之後,就可以寫入內容為表名和表 ID 的 Table_map_log_event 了。

寫入 Table_map_log_event 之後,接下來寫入的 binlog event 就是包含插入記錄所有欄位的 Write_rows_log_event 了。

最後,執行 COMMIT 語句時,會產生內容為 COMMITXid_log_event,並寫入 trx_cache。

5. 怎麼寫入 trx_cache?

事務執行過程中,所有 binlog event 都會先寫入 trx_cache 的 buffer,buffer 大小預設為 32K。

如果 buffer 剩餘空間不夠寫入一個 binlog event,buffer 和臨時檔案怎麼協同合作,來完成這個 binlog event 的寫入操作?

接下來,我們就來聊聊,寫入一個 binlog event 到 trx_cache 的流程:

  • 判斷 buffer 剩餘空間是否足夠寫入這個 binlog event。
  • 如果足夠,直接把 binlog event 寫入 buffer,流程結束。
  • 如果不夠,用 binlog event 前面的部分內容填滿 buffer,然後,把 buffer 中所有內容寫入臨時檔案,再清空 buffer,以備複用。
  • 接著判斷 binlog event 剩餘內容是否大於等於 4096 位元組(IO_SIZE)。
  • 如果剩餘內容大於等於 4096 位元組,則把剩餘內容前面的 N * 4096 位元組寫入臨時檔案。
    對於剩餘內容位元組數不能被 4096 整除的情況,最後還會剩下不足 4096 位元組的內容,這部分內容會寫入 buffer。
  • 如果剩餘內容小於 4096 位元組,直接把 binlog event 中剩餘的所有內容都寫入 buffer。

6. 總結

trx_cache 分為兩級:記憶體(buffer)、臨時檔案。

事務執行過程中,產生的所有 binlog event 都要寫入 trx_cache。

binlog event 寫入 trx_cache,通常情況下,都會先寫入 buffer,寫滿 buffer 之後,再把 buffer 中所有內容都寫入臨時檔案,最後清空 buffer。

本期問題:如果 buffer 是空的,接下來要寫入一個 86K 的 binlog event 到 trx_cache,寫入流程是什麼樣的?歡迎大家留言交流。

下期預告:MySQL 核心模組揭秘 | 07 期 | 二階段提交 (1) prepare 階段。

相關文章