做了那麼多準備工作,終於要啓動 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 期 | 讀事務和只讀事務的變形記。