【MySQL 讀書筆記】RR(REPEATABLE-READ)事務隔離詳解


這篇我覺得有點難度,我會更慢的更詳細的分析一些 case 。

 

MySQL 的默認事務隔離級別和其他幾個主流數據庫隔離級別不同,他的事務隔離級別是 RR(REPEATABLE-READ) 其他的主流數據庫比如 oracle 通常是 RC(READ-COMMITTED)

關於數據庫有哪些隔離級別我這里就不詳細闡述了,大概是什么特性我這里就不闡述了大家可以自行翻閱資料,讓我們聚焦這兩個最重要的隔離級別在一些查詢更新的時候會出現什么樣的特性表達。

 

當我們使用 RR 的時候,事務啟動的時候會創建一個視圖 read-view,之后事務執行期間,即使有其他事務修改了數據,事務看到的仍然和她啟動的時候看到的一樣。也就是說,一個在可重復讀隔離級別下執行的事務不受外界影響。

但是上一篇分享鎖的文章里面我們也提到了,如果說另外一個事務對表加了行鎖,他會被鎖住進入等待狀態。那么當等待狀態結束,這個事務自己要獲取行鎖更新數據的時候,他讀到的值是什么呢?

來看個例子

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);

 

然后使用這個事務啟動順序來測試

這里有幾個點需要注意,我們在數據庫使用事務 begin/start transaction 命令並不是一個事務的起點,在執行到第一個操作 InnooDB 表的語句,事務才被真正啟動。

如果我們要馬上啟動一個一致性讀事務使用 start transaction with consistent snapshot 這個。

它的含義是:執行 start transaction 同時建立本事務一致性讀的 snapshot . 而不是等到執行第一條語句時,才開始事務,並且建立一致性讀的 snapshot 。

文章中說,這個順序查詢,事務 B 查詢到的 id =1 的 k 值是3,事務 A 查詢到的值 k 值是 1。我起初也無法理解,下面讓我們一步一步來得出結論。

 

快照”在 MVCC 里是怎么工作的?

 在可重復讀隔離級別下,事務在啟動的時候就拍了快照。InnoDB 里面每個事務有一個唯一的事務 ID, 叫 transaction id。它是在事務開始的時候向 InnoDB 的事務系統申請的。是按照申請順序遞增的。每行數據也有多個版本,每次事務更新數據的時候,都會生成一個新的數據版本,並且把 transaction id 賦值給這個數據版本的事務 ID, row trx_id。舊的數據版本要保留,並且新的數據版本中,能夠有信息可以直接拿到。

當前最新版本是 V4 V4 版本是經過一系列更新之后得到的最新的狀態。 他的 row trx_id = 25。

u1 u2 u3 都是 undo log 的記錄,我們可以在 v4 通過 undolog 恢復到版本v1 v2 v3 並不是物理上真實存在的。

這里按照可重復度的定義,當一個事務啟動的時候,能偶看到所有已經提交的事務結果。但是之后,這個事務執行期間,其他事務的更新對它不可見。 

因此一個在 RR 事務級別啟動一個事務的時候聲明說,以我啟動的時刻為准,如果一個數據版本是在我啟動之前生成的就認,如果是我啟動之后才生成的,就不認,我必須找到他的上一個版本。如果上一個版本也不可見,就繼續往前找。當然如果是這個事務自己本身更新的數據,它自己是要認的。

 

在實現上, InnoDB 為每個事務構造了一個數組,用來保存這個事務啟動的時候,當前正在“活躍”的所有事務 ID,“活躍”指的是,啟動了但是還沒有提交。

這個數組組成了一個類似這樣的東西

低水位:指獲取到這個數組內的 trx_id 最小值。

高水位:指獲取到的這個數組內的 trx_id 最大值 + 1

 

這樣對於當前事務啟動的瞬間來說,一個數據版本的 row trx_id 有以下幾種可能。

1. 如果落在綠色部分,標示這個版本是已經提價的事務護着當前自己事務生成的,這個數據是可見的。

2. 如果落在紅色部分,標示這個版本是由將來的事務生成的,是不可見的。

3. 如果落在黃色部分,

a. 如果 row trx_id 在數組中,標示這個版本是由還沒有提交哦的事務生成的,不可見。

b. 如果 row trx_id 不在數組中,標示這個版本是已經提交了的事務生成的,可見。

 

下面我們用上面的理論拉來解釋一下為什么我第一張圖的查詢結果會是那個樣子。 

1. 假設 事務 A 開始前,系統里面沒有哦活躍事務 ID .

2. 事務 A 開始時候的事務版本號為 100 事務 B 開始時候的事務版本號為 101 事務 C 開始時候的事務版本號為 102。

3. 三個事務開始前 id =1 k =1 的數據 row trx_id 是 90.

 

事務 A 開始的時候事務數組為 [100]

事務 B 開始時候的事務數組為 [100, 101]

事務 C 開始時候的事務數組為 [100, 101, 102]

 

祖先版本是 id =1, k=2 對應版本是 90。

第一個有效更新的事務是 C 當 C 完成更新之后 102 版本就是對應 id = 1 k = 2.

這個時候由於版本 102 無論對於事務 B 還是 事務 A 都處於高水位,所以都是不見的。也就是說現在我們執行

select * from t where id = 1 會發現

mysql> select * from t;
+----+------+
| id | k    |
+----+------+
|  1 |    1 |
|  2 |    2 |
+----+------+

 

這個時候第二個有效事務 B 更新了,把數據從 id =1 k = 2 變更為 id =1 k=3,這個時候數據的最新版本變成了 101,而102 變成了歷史版本。

注意這個時候事務 B 會發現自己的數據沒有經過 k=2 這一步 直接就變成 k =3 了。。。因為事務 C 更新並且提交了,我們在這個基礎上增加會讀取到 102 的更新。

但是事務 A 還是無法讀取到 102 版本和 101 版本的更新,因為他們都在高水位,所以最終讀取到的還是 id =1 k=1。

 

但是真實的情況 事務 A 是會去判斷的,也就是說他會找到最后一個被更新的版本 101 會發現是高水位不可見。

接着找上一個版本 102 還是高水位不可見。

最后找到原始版本 90 處於低於低水位的區域可見。

這樣執行下來,雖然期間這一行數據被修改過,但是事務 A 無論在什么時候查詢,看到這行數據的結果都是一致的,所以我們稱為一致性讀。

 

RR 的更新數據都是先讀后寫的,這個讀就是當前讀。這可以解釋為什么我們可以跳過 k=2 直接 k=3。因為在 k=1 的時候進行當前讀發現 k=2 了,然后再 +1 就 k=3 了。

當然 select xx for update | lock in share mode 也是當前讀。

我覺得 此片文章到此就差不多了。感覺老師后面以緊接着介紹了一些無關緊要的東西 包括 RC 的情況。給本來就比較難以理解的情況搞得更復雜了。

 

現在我解除到大部分公司的 DB 使用 MySQL 都會將事務隔離級別從默認的 RR 設置到 RC,更好理解也可以更方便的用樂觀鎖來保證數據的一致性。並且我感覺如果不使用當前讀,可能還會對性能有一定的影響。畢竟上面介紹到的流程里面,是需要掃 undolog 參與的,感覺這些可能都會有一定的性能損失。

 

 

Reference:

本讀書筆記皆來自發布在極客時間的 林曉斌(丁奇)的 MySQL 實戰45講:

極客時間版權所有: https://time.geekbang.org/ 版權所有: 

https://time.geekbang.org/column/article/70562


免責聲明!

本站轉載的文章為個人學習借鑒使用,本站對版權不負任何法律責任。如果侵犯了您的隱私權益,請聯系本站郵箱yoyou2525@163.com刪除。



 
粵ICP備18138465號   © 2018-2025 CODEPRJ.COM