1. 程式人生 > 其它 >06 : 事物到底是隔離還是不隔離

06 : 事物到底是隔離還是不隔離

在MySQL 中有兩個“檢視”的概念:

  • 一個是view, 它是一個用於查詢語句定義的虛擬表,在呼叫的時候質詢查詢語句並生成結果,建立檢視的語法是create view..., 而它的查詢辦法與表一樣
  • 另一個是InnoDB在實現MVCC時用到的一致性檢視,即consistent read view, 用於支援RC 讀提交,RR 可重複讀,隔離級別的實現。

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

InnoDB裡面每個事務有一個唯一的事務ID,叫作transaction id。它是在事務開始的時候向InnoDB的事務系統申請的,是按申請順序嚴格遞增的。
而每行資料也都是有多個版本的。每次事務更新資料的時候,都會生成一個新的資料版本,並且把transaction id賦值給這個資料版本的事務ID,記為row trx_id。同時,舊的資料版本要保留,並且在新的資料版本中,能夠有資訊可以直接拿到它。

圖中虛線框裡是同一行資料的4個版本,當前最新版本是V4,k的值是22,它是被transaction id 為25的事務更新的,因此它的row trx_id也是25。

InnoDB是怎麼定義那個“100G”的快照的。
按照可重複讀的定義,一個事務啟動的時候,能夠看到所有已經提交的事務結果。但是之後,這個事務執行期間,其他事務的更新對它不可見。因此,一個事務只需要在啟動的時候宣告說,“以我啟動的時刻為準,如果一個數據版本是在我啟動之前生成的,就認;如果是我啟動以後才生成的,我就不認,我必須要找到它的上一個版本”。當然,如果“上一個版本”也不可見,那就得繼續往前找。還有,如果是這個事務自己更新的資料,它自己還是要認的。

在實現上, InnoDB為每個事務構造了一個數組,用來儲存這個事務啟動瞬間,當前正在“活躍”的所有事務ID。“活躍”指的就是,啟動了但還沒提交。
數組裡面事務ID的最小值記為低水位,當前系統裡面已經建立過的事務ID的最大值加1記為高水位。
這個檢視陣列和高水位,就組成了當前事務的一致性檢視(read-view)。
而資料版本的可見性規則,就是基於資料的row trx_id和這個一致性檢視的對比結果得到的。
這個檢視陣列把所有的row trx_id 分成了幾種不同的情況。

  • 如果落在綠色部分,表示這個版本是已提交的事務或者是當前事務自己生成的,這個資料是可見的
  • 如果落在紅色部分,表示這個版本是由將來啟動的事務生成的,是肯定不可見的;
  • 如果落在黃色部分,那就包括兩種情況
    a. 若 row trx_id在陣列中,表示這個版本是由還沒提交的事務生成的,不可見;
    b. 若 row trx_id不在陣列中,表示這個版本是已經提交了的事務生成的,可見。
    有了這個聲明後,系統裡面隨後發生的更新,是不是就跟這個事務看到的內容無關了呢?因為之後的更新,生成的版本一定屬於上面的2或者3(a)的情況,而對它來說,這些新的資料版本是不存在的,所以這個事務的快照,就是“靜態”的了。

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

  • 事務A開始前,系統裡面只有一個活躍事務ID是99;
  • 事務A、B、C的版本號分別是100、101、102,且當前系統裡只有這四個事務;
  • 三個事務開始前,(1,1)這一行資料的row trx_id是90。
    這樣,事務A的檢視陣列就是[99,100], 事務B的檢視陣列是[99,100,101], 事務C的檢視陣列是[99,100,101,102]。

    事務A的語句返回的結果 k = 1
    第一個有效更新是事務C,把資料從(1,1)改成了(1,2)。這時候,這個資料的最新版本的row trx_id是102,而90這個版本已經成為了歷史版本。
    第二個有效更新是事務B,把資料從(1,2)改成了(1,3)。這時候,這個資料的最新版本(即row trx_id)是101,而102又成為了歷史版本。
    你可能注意到了,在事務A查詢的時候,其實事務B還沒有提交,但是它生成的(1,3)這個版本已經變成當前版本了。但這個版本對事務A必須是不可見的,否則就變成髒讀了。
    好,現在事務A要來讀資料了,它的檢視陣列是[99,100]。當然了,讀資料都是從當前版本讀起的。所以,事務A查詢語句的讀資料流程是這樣的:
  • 找到(1,3)的時候,判斷出row trx_id=101,比高水位大,處於紅色區域,不可見;
  • 接著,找到上一個歷史版本,一看row trx_id=102,比高水位大,處於紅色區域,不可見;
  • 再往前找,終於找到了(1,1),它的row trx_id=90,比低水位小,處於綠色區域,可見。

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

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

更新邏輯

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


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

而讀提交的邏輯和可重複讀的邏輯類似,它們最主要的區別是:

  • 在可重複讀隔離級別下,只需要在事務開始的時候建立一致性檢視,之後事務裡的其他查詢都共用這個一致性檢視;
  • 在讀提交隔離級別下,每一個語句執行前都會重新算出一個新的檢視。

下面是讀提交時的狀態圖,可以看到這兩個查詢語句的建立檢視陣列的時機發生了變化,就是圖中的read view框。

(1,3)還沒提交,屬於情況1,不可見;
(1,2)提交了,屬於情況3,可見。
所以,這時候事務A查詢語句返回的是k=2。
顯然地,事務B查詢結果k=3。

== 對於可重複讀,查詢只承認在事務啟動前就已經提交完成的資料;==
== 對於讀提交,查詢只承認在語句啟動前就已經提交完成的資料;==