事務都以讀事務身份啟動,讀事務和隻讀事務會在需要時發生變化,它們會怎麼變化?這是本文要回答的問題。
![](https://news.xinpengboligang.com/upload/keji/8320f2e8cd8c202eb5e3f89971a6b881.jpeg)
作者:操盛春,愛可生技術專傢,公眾號『一樹一溪』作者,專註於研究 MySQL 和 OceanBase 源碼。
愛可生開源社區出品,原創內容未經授權不得隨意使用,轉載請聯系小編並註明來源。
本文基於 MySQL 8.0.32 源碼,存儲引擎為 InnoDB。
1. update、delete
後面小節的內容和 update、delete 有關,我們先簡單介紹一下這兩類 SQL 語句的執行流程。
以更新一條記錄為例,update 語句的簡化執行流程如下:
- server 層要求 InnoDB 從表中讀取一條記錄。
- InnoDB 返回記錄之後,server 層判斷這條記錄是否匹配 where 條件。
- 如果匹配,用 update 語句 set 子句中指定的各字段值,替換 InnoDB 返回記錄的對應字段值。
- 替換字段值得到完整記錄之後,server 層觸發 InnoDB 更新記錄。
以刪除一條記錄為例,delete 語句的簡化執行流程如下:
- server 層要求 InnoDB 從表中讀取一條記錄。
- InnoDB 返回記錄之後,server 層判斷這條記錄是否匹配 where 條件。
- 如果匹配,server 層觸發 InnoDB 刪除記錄。
2. 讀事務
上一期我們介紹過,事務真正啟動於執行第一條 SQL 語句時,如果第一條 SQL 語句是 select、update、delete,事務會以讀事務的身份啟動。
讀事務啟動時,沒有分配事務 ID 和回滾段,事務對象也沒有加入到 trx_sys->rw_trx_list 鏈表。
根據我們使用 MySQL 的經驗,以讀事務身份啟動的事務,不僅能正常執行改變(插入、更新、刪除)表中數據的操作,還支持 MVCC、回滾。
對於啟動時沒有分配事務 ID 和回滾段的讀事務來說,這是怎麼做到的呢?
有一句話能夠很好的回答這個問題,就是以發展的眼光看問題!
以讀事務身份啟動的事務,並不意味著一直都是讀事務,它可以在某些時間點變成讀寫事務。
根據執行的第一條 SQL 語句不同,讀事務變成讀寫事務的時間點可以分為兩類:
第一類:第一條 SQL 語句是 update 或 delete。
在 update 或 delete 語句執行過程中,讀事務就會變成讀寫事務。
發生變化的具體時間點,又取決於這兩類 SQL 語句更新或刪除記錄的第一個表是什麼類型。
如果第一個表是用戶普通表,InnoDB 從表中讀取一條記錄之前,會給表加意向排他鎖(IX)。
加意向排他鎖時,如果以下三個條件成立,InnoDB 就會把這個事務變成讀寫事務:
- 事務還沒有為用戶普通表分配回滾段。
- 事務 ID 為 0,說明這個事務現在還是讀事務。
- 事務的隻讀標識 trx->read_only = false,說明這個事務可以變成讀寫事務。
讀事務變成讀寫事務,InnoDB 主要做 3 件事:
- 分配事務 ID。
- 為用戶普通表分配回滾段。
- 把事務對象加入 trx_sys->rw_trx_list 鏈表。
如果第一個表是用戶臨時表,因為它的可見范圍隻限於創建這個表的數據庫連接之內,其它數據庫連接中執行的事務都看不到這個表,更不能改變表中的數據,所以,update、delete 語句改變用戶臨時表中的數據,不需要加意向排他鎖。
讀事務變成讀寫事務的操作會延遲到 server 層觸發 InnoDB 更新或刪除記錄之後,InnoDB 執行更新或刪除操作之前。
在這個時間節點,如果以下三個條件成立,InnoDB 就會把這個事務變成讀寫事務:
- 事務已經啟動了。
- 事務 ID 為 0,說明這個事務現在還是讀事務。
- 事務的隻讀標識 trx->read_only = false,說明這個事務可以變成讀寫事務。
有一點需要說明,改變用戶臨時表的數據觸發讀事務變成讀寫事務,不會分配用戶臨時表回滾段,需要等到為某個用戶臨時表第一次寫 Undo 日志時才分配。
第二類:第一條 SQL 語句是 select。
在 select 語句執行過程中,讀事務不會變成讀寫事務;這條 SQL 語句執行完之後、事務提交之前,第一次執行 insert、update、delete 語句時,讀事務才會變成讀寫事務。
一個讀事務變成讀寫事務的操作,隻會發生一次,發生變化的具體時間點,取決於最先碰到哪種 SQL 語句。
如果最先碰到 insert 語句,server 層準備好要插入的記錄的每個字段之後,會觸發 InnoDB 執行插入操作。
執行插入操作之前,如果以下三個條件成立,InnoDB 就會把這個事務變成讀寫事務:
- 事務已經啟動了。
- 事務 ID 為 0,說明這個事務現在還是讀事務。
- 事務的隻讀標識 trx->read_only = false,說明這個事務可以變成讀寫事務。
如果最先碰到的是 update 或 delete 語句,讀事務變成讀寫事務的具體時間點,參照第一類中關於用戶普通表、用戶臨時表的介紹。
3. 隻讀事務
隻讀事務是讀事務的特例,以 start transaction 開始一個事務時,如果包含了 read only 關鍵字,這個事務就是一個隻讀事務。例如:
start transaction read only
隻讀事務不能改變(插入、更新、刪除)系統表、用戶普通表的數據,但是能改變用戶臨時表的數據。
改變用戶臨時表的數據,同樣需要為事務分配事務 ID,為用戶臨時表分配回滾段。根據隻讀事務執行的第一條 SQL 語句不同,這兩個操作發生的時間點也可以分為兩類。
第一類:第一條 SQL 語句是 update 或 delete。
在 update 或 delete 語句執行過程中,server 層觸發 InnoDB 更新或刪除記錄之後,InnoDB 執行更新或刪除操作之前,如果以下三個條件成立,InnoDB 就為這個事務分配事務 ID、為用戶臨時表分配回滾段:
- 事務已經啟動了。
- 事務 ID 為 0。
- 事務是個隻讀事務(trx->read_only = true)。
第二類:第一條 SQL 語句是 select。
在 select 語句執行過程中,不會分配事務 ID 和用戶臨時表的回滾段;這條 SQL 執行完之後、事務提交之前,第一次執行 insert、update、delete 語句時,才會執行這兩個操作。
對於一個隻讀事務,這兩個操作隻會執行一次,執行的具體時間點,取決於最先碰到哪種 SQL 語句。
如果最先碰到 insert 語句,server 層準備好要插入的記錄的每個字段之後,會觸發 InnoDB 執行插入操作。
執行插入操作之前,如果以下三個條件成立,InnoDB 會為這個隻讀事務分配事務 ID、為用戶臨時表分配回滾段:
- 事務已經啟動了。
- 事務 ID 為 0。
- 事務是個隻讀事務(trx->read_only = true)。
如果最先碰到的是 update 或 delete 語句,執行這兩個操作的具體時間點,參照第一類的介紹。
4. 總結
以讀事務或隻讀事務身份啟動的事務:
- 如果執行的第一條 SQL 語句是 update 或 delete,在 SQL 語句執行過程中,讀事務會變成讀寫事務,隻讀事務會分配事務 ID 和用戶臨時表的回滾段。
- 如果執行的第一條 SQL 語句是 select,在後續第一次執行 insert、update、delete 三種語句的其中一種時,讀事務會變成讀寫事務,隻讀事務會分配事務 ID 和用戶臨時表的回滾段。
讀事務變成讀寫事務,InnoDB 主要做 3 件事:
- 分配事務 ID。
- 為用戶普通表分配回滾段。
- 把事務對象加入 trx_sys->rw_trx_list 鏈表。
本期問題:關於本期內容,如有問題,歡迎留言交流。
下期預告:MySQL 核心模塊揭秘 | 06 期 | 事務提交之前,binlog 寫到哪裡?
更多技術文章,請訪問:https://opensource.actionsky.com/
![](https://news.xinpengboligang.com/upload/keji/ef66b976793dcfece6fd68784633121e.jpeg)