動態

詳情 返回 返回

MySQL 核心模塊揭秘 | 04 期 | 終於要啓動事務了 - 動態 詳情

做了那麼多準備工作,終於要啓動 InnoDB 事務了。

作者:操盛春,愛可生技術專家,公眾號『一樹一溪』作者,專注於研究 MySQL 和 OceanBase 源碼。

愛可生開源社區出品,原創內容未經授權不得隨意使用,轉載請聯繫小編並註明來源。

本文基於 MySQL 8.0.32 源碼,存儲引擎為 InnoDB。

1. 啓動事務

在《BEGIN 語句會馬上啓動事務嗎?》這篇文章中,我們介紹過開始一個事務的 8 種 SQL 語句:

/* 1 */ BEGIN
/* 2 */ BEGIN WORK
/* 3 */ START TRANSACTION
/* 4 */ START TRANSACTION READ WRITE
/* 5 */ START TRANSACTION READ ONLY
/* 6 */ START TRANSACTION WITH CONSISTENT SNAPSHOT
/* 7 */ START TRANSACTION WITH CONSISTENT SNAPSHOT, READ WRITE
/* 8 */ START TRANSACTION WITH CONSISTENT SNAPSHOT, READ ONLY

語句 1 ~ 5 都不會馬上啓動新事務,只會給執行這些語句的線程打上 OPTION_BEGIN 標記,在這之後執行第一條 SQL 時,才會真正的啓動事務。

在《我是一個事務,請給我一個對象》這篇文章中,我們介紹過:InnoDB 給事務分配一個對象(trx)之後,該對象的狀態屬性(state)值為 TRX_STATE_NOT_STARTED,表示事務還未開始。

啓動事務最重要的事情之一,就是修改事務狀態了,代碼是這樣的:

trx->state.store(TRX_STATE_ACTIVE, std::memory_order_relaxed)

事務狀態從 TRX_STATE_NOT_STARTED 修改為 TRX_STATE_ACTIVE,表示事務已經啓動,是個活躍事務了。

我們執行 show engine innodb status 可能會看到類似下面的內容:

LIST OF TRANSACTIONS FOR EACH SESSION:
0 lock struct(s), heap size 1192, 0 row lock(s)
---TRANSACTION 206242, ACTIVE 42 sec

其中,ACTIVE 就來源於事務的 TRX_STATE_ACTIVE 狀態。

2. 讀事務

事務啓動於執行第一條 SQL 語句時,如果第一條 SQL 語句是 select、update、delete,InnoDB 會以讀事務的身份啓動新事務。

讀事務的 ID 會被設置為 0:

trx->id = 0;

對於 ID 等於 0 的事務,查詢 information_schema.innodb_trx 表得到的 trx_id 字段值並不是 0,而是一串比較長的數字:

************[ 1. row ]************
trx_id      | 281480261177256
trx_state   | RUNNING
trx_started | 2023-12-24 22:39:45
...

上面的 trx_id 字段值是這樣計算出來的:

  • 把事務對象的內存地址轉換為十進制數字。
  • 用上一步得到的數字加上 281474976710656。這個數字是 6 字節能夠存放的最大事務 ID + 1,6 字節是記錄中隱藏的事務 ID 字段(DB_TRX_ID)佔用的字節數。
  • 經過以上兩步計算,就得到了 trx_id 字段值。

以上面查詢出來的事務為例,事務對象的內存地址為 0x000000013afa8fa8。內存地址以 0x 開頭,是十六進制,轉換為十進制得到 5284466600,再加上 281474976710656 就得到了 trx_id 字段值 281480261177256

通過這個計算邏輯,我們可以根據 information_schema.innodb_trx 表中 trx_id 字段值判斷事務是否分配了 ID:

  • 如果 trx_id 字段值大於等於 281474976710656,説明該事務沒有分配 ID。
  • 如果 trx_id 字段值小於 281474976710656,説明該事務分配了 ID。

3. 只讀事務

只讀事務是讀事務的一個特例,從字面上看,它是不能改變(插入、修改、刪除)表中數據的。

然而,這個只讀並不是絕對的,只讀事務不能改變系統表、用户普通表的數據,但是可以改變用户臨時表的數據。

作為讀事務的特例,只讀事務也要遵守讀事務的規則,事務 ID 應該為 0。

只讀事務操作系統表、用户普通表,只能讀取表中數據,事務 ID 為 0(即不分配事務 ID)沒問題。

只讀事務操作用户臨時表,可以改變表中數據,而用户臨時表也支持事務 ACID 特性中的 3 個(ACI),這就需要分配事務 ID 了。

如果只讀事務執行的第一條 SQL 語句就是插入記錄到用户臨時表的 insert,事務啓動過程中會分配事務 ID。我們可以通過一個例子來確認這一點:

-- 開始只讀事務之前創建一個用户臨時表
-- 因為只讀事務裏不能創建用户臨時表(會報錯)
create temporary table t_tmp (
    id int unsigned auto_increment primary key,
  i1 int not null default 0,
  i2 int not null default 0
) engine = InnoDB default charset utf8;

-- 標識要開啓一個只讀事務
start transaction read only;

-- 往用户臨時表中插入一條記錄
insert into t_tmp(i1, i2) values (10, 100);

查詢 information_schema.innodb_trx 表可以看到只讀事務分配了事務 ID:

select * from information_schema.innodb_trx\G

************[ 1. row ]************
trx_id      | 206266
trx_state   | RUNNING
trx_started | 2023-12-24 21:44:51
...

trx_id 字段值 206266 小於 281474976710656,説明這個只讀事務分配了事務 ID。

4. 讀寫事務

如果事務執行的第一條 SQL 語句是 insert,這個事務就會以讀寫事務的身份啓動。

讀寫事務的啓動過程,主要會做這幾件事:

  • 為用户普通表分配回滾段,用於寫 Undo 日誌。
  • 分配事務 ID。
  • 把事務對象加入 trx_sys->rw_trx_list 鏈表。這個鏈表記錄了所有讀寫事務。

    UT_LIST_ADD_FIRST(trx_sys->rw_trx_list, trx);

5. 內部事務

用户事務以什麼身份啓動,取決於執行的第一條 SQL 是什麼。

和用户事務不一樣,InnoDB 啓動內部事務都是為了改變表中數據,所以,內部事務都是讀寫事務

作為讀寫事務,所有內部事務都會加入到 trx_sys->rw_trx_list 鏈表中。

6. 總結

InnoDB 開啓內部事務,是為了改變表中數據,所以,內部事務都以讀寫事務的身份啓動。

用户事務可能會讀取、改變表中數據,根據執行的第一條 SQL 語句不同,以不同身份啓動:

  • 執行的第一條 SQL 語句是 select、update、delete,以讀事務身份啓動事務。
  • 執行的第一條 SQL 語句是 insert,以讀寫事務身份啓動事務。
    如果只讀事務執行的第一條 SQL 語句是插入記錄到用户臨時表的 insert,也會分配事務 ID。

本期問題:mysql_trx_list、rw_trx_list 這兩個鏈表分別用來幹什麼?歡迎留言交流。

下期預告:MySQL 核心模塊揭秘 | 05 期 | 讀事務和只讀事務的變形記。

user avatar ivictor 頭像 zhouzhenchao 頭像 mirrorship 頭像 kerrycode 頭像 liu_486 頭像 xiaoyi_ces 頭像
點贊 6 用戶, 點贊了這篇動態!
點贊

Add a new 評論

Some HTML is okay.