MySQL 核心模塊揭秘 05 期 讀事務和隻讀事務的變形記

2024年2月6日 19点热度 0人点赞

事務都以讀事務身份啟動,讀事務和隻讀事務會在需要時發生變化,它們會怎麼變化?這是本文要回答的問題。

作者:操盛春,愛可生技術專傢,公眾號『一樹一溪』作者,專註於研究 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/