動態

詳情 返回 返回

MySQL 核心模塊揭秘 | 06 期 | 事務提交之前,binlog 寫到哪裏? - 動態 詳情

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 階段。

user avatar seatunnel 頭像 wobushiliaojian 頭像 feixiangdemojing 頭像
點贊 3 用戶, 點贊了這篇動態!
點贊

Add a new 評論

Some HTML is okay.