1. 程式人生 > 實用技巧 >MySQL實戰45講-08 事務到底是隔離的還是不隔離的?

MySQL實戰45講-08 事務到底是隔離的還是不隔離的?

如下示例中,事務 B 查到的 k 的值是 3,而事務 A 查到的 k 的值是 1。為什麼?

mysql> CREATE TABLE `t` (
  `id` int(11) NOT NULL,
  `k` int(11) DEFAULT NULL,
  PRIMARY KEY (`id`)
) ENGINE=InnoDB;
insert into t(id, k) values(1,1),(2,2);

註釋:

  • 事務的啟動時機
  1. begin/start transaction 命令並不是一個事務的起點,在執行到它們之後的第一個操作 InnoDB 表的語句,事務才真正啟動。
  2. 如果你想要馬上啟動一個事務,可以使用 start transaction with consistent snapshot 這個命令。
  • 例子中如果沒有特別說明,都是預設 autocommit=1。
  • “檢視”的概念:
  1. 一個是 view。它是一個用查詢語句定義的虛擬表,在呼叫的時候執行查詢語句並生成結果。建立檢視的語法是 create view … ,而它的查詢方法與表一樣。
  2. 另一個是 InnoDB 在實現 MVCC 時用到的一致性讀檢視,即 consistent read view,用於支援 RC(Read Committed,讀提交)和 RR(Repeatable Read,可重複讀)隔離級別的實現。

“快照”在 MVCC 裡是怎麼工作的?

InnoDB 裡面每個事務有一個唯一的事務 ID,叫作 transaction id

。它是在事務開始的時候向 InnoDB 的事務系統申請的,是按申請順序嚴格遞增的。
每次事務更新資料的時候,都會生成一個新的資料版本,並且把 transaction id 賦值給這個資料版本的事務 ID,記為 row trx_id

資料表中的一行記錄,其實可能有多個版本 (row),每個版本有自己的 row trx_id。如圖 2 所示,就是一個記錄被多個事務連續更新後的狀態。

其中U1、U2、U3是undo log;V1、V2、V3都是通過當前版本V4和undo log計算出來的。

InnoDB 利用了“所有資料都有多個版本”的這個特性,實現了“秒級建立快照”的能力。

事務 A 查到的 k 的值是 1。為什麼?

InnoDB 為每個事務構造了一個數組,用來儲存這個事務啟動瞬間,當前啟動了但還沒提交的所有事務 ID。

  • 如果一個數據版本是在當前事務啟動之前生成的,就認;如果是在當前事務以後生成的,就不認,必須要找到它的上一個版本”。
  • 如果是當前事務自己更新的資料,它自己還是要認的。

也即,一個數據版本,對於一個事務檢視來說,除了自己的更新總是可見以外,有三種情況:

  • 版本未提交,不可見;
  • 版本已提交,但是是在檢視建立後提交的,不可見;
  • 版本已提交,而且是在檢視建立前提交的,可見。

事務 B 查到的 k 的值是 3,為什麼?

更新資料都是先讀後寫的,而這個讀,只能讀當前的值,稱為“當前讀”(current read)。

除了 update 語句外,select 語句如果加鎖,也是當前讀。下面這兩個 select 語句,就是分別加了讀鎖(S 鎖,共享鎖)和寫鎖(X 鎖,排他鎖)。

mysql> select k from t where id=1 lock in share mode;
mysql> select k from t where id=1 for update;

假設事務 C' 不是馬上提交的,而是在事務B之後提交,會怎麼樣?

此時,章節07 行鎖功過:怎麼減少行鎖對效能的影響?“兩階段鎖協議”生效。事務 C’沒提交,也就是說 (1,2) 這個版本上的寫鎖還沒釋放。而事務 B 是當前讀,必須要讀最新版本,而且必須加鎖,因此就被鎖住了,必須等到事務 C’釋放這個鎖,才能繼續它的當前讀。

事務的可重複讀的能力是怎麼實現的?

可重複讀的核心就是一致性讀(consistent read);而事務更新資料的時候,只能用當前讀。如果當前的記錄的行鎖被其他事務佔用的話,就需要進入鎖等待。

假設還是事務A、B、C(不是C'),但是隔離級別是讀提交

在讀提交隔離級別下,每一個語句執行前都會重新算出一個新的檢視。

事務 B 查詢結果 k=3。

對於事務 A:

  • (1,3) 還沒提交,屬於情況 1,不可見;
  • (1,2) 提交了,屬於情況 3,可見。