這篇我以爲有點難度,我會更慢的更詳細的分析一些 case 。mysql
MySQL 的默認事務隔離級別和其餘幾個主流數據庫隔離級別不一樣,他的事務隔離級別是 RR(REPEATABLE-READ) 其餘的主流數據庫好比 oracle 一般是 RC(READ-COMMITTED)sql
關於數據庫有哪些隔離級別我這裏就不詳細闡述了,大概是什麼特性我這裏就不闡述了你們能夠自行翻閱資料,讓咱們聚焦這兩個最重要的隔離級別在一些查詢更新的時候會出現什麼樣的特性表達。數據庫
當咱們使用 RR 的時候,事務啓動的時候會建立一個視圖 read-view,以後事務執行期間,即便有其餘事務修改了數據,事務看到的仍然和她啓動的時候看到的同樣。也就是說,一個在可重複讀隔離級別下執行的事務不受外界影響。數組
可是上一篇分享鎖的文章裏面咱們也提到了,若是說另一個事務對錶加了行鎖,他會被鎖住進入等待狀態。那麼當等待狀態結束,這個事務本身要獲取行鎖更新數據的時候,他讀到的值是什麼呢?oracle
來看個例子性能
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 表的語句,事務才被真正啓動。spa
若是咱們要立刻啓動一個一致性讀事務使用 start transaction with consistent snapshot 這個。code
它的含義是:執行 start transaction 同時創建本事務一致性讀的 snapshot . 而不是等到執行第一條語句時,纔開始事務,而且創建一致性讀的 snapshot 。blog
文章中說,這個順序查詢,事務 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