事務隔離

初始化表mysql

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

 

 

 

 

 

對於事務來說,若是默認autocommit=1的話,只有執行到語句纔會開啓事務,這裏的AB咱們都是可使用start transaction with consistent snapshot 這個命令,提早開啓事務。sql

 

這裏的查詢結果是什麼呢,直接說答案  事務A是1  事務B是3數據庫

 

分析:數組

在MySQL裏,有兩個「視圖」的概念:session

一個是view。它是一個用查詢語句定義的虛擬表,在調用的時候執行查詢語句並生成結果。建立視圖的語法是create view ... ,而它的查詢方法與表同樣。併發

另外一個是InnoDB在實現MVCC時用到的一致性讀視圖,即consistent read view,用於支持RC(Read Committed,讀提交)和RR(Repeatable Read,可重複讀)隔離級別的實現。spa

 

「快照」在MVCC裏是怎麼工做的?

 

在可重複讀隔離級別下,事務在啓動的時候就「拍了個快照」。注意,這個快照是基於整庫的。這裏並非直接會讓數據庫備份,3d

而每行數據也都是有多個版本的。每次事務更新數據的時候,都會生成一個新的數據版本,而且把transaction id賦值給這個數據版本的事務ID,記爲row trx_id。同時,舊的數據版本要保留,而且在新的數據版本中,可以有信息能夠直接拿到它。日誌

也就是說,數據表中的一行記錄,其實可能有多個版本(row),每一個版本有本身的row trx_id。code

 

 

              圖2

語句更新會生成undo log(回滾日誌)嗎?那麼,undo log在哪呢?

 

實際上,圖2中的三個虛線箭頭,就是undo log;而V一、V二、V3並非物理上真實存在的,而是每次須要的時候根據當前版本和undo log計算出來的。好比,須要V2的時候,就是經過V4依次執行U三、U2算出來。

 

按照可重複讀的定義,一個事務啓動的時候,可以看到全部已經提交的事務結果。可是以後,這個事務執行期間,其餘事務的更新對它不可見。

 

 

 

 這樣,對於當前事務的啓動瞬間來講,一個數據版本的row trx_id,有如下幾種可能:

  1.若是落在綠色部分,表示這個版本是已提交的事務或者是當前事務本身生成的,這個數據是可見的;

  2.若是落在紅色部分,表示這個版本是由未來啓動的事務生成的,是確定不可見的;

  3.若是落在黃色部分,那就包括兩種狀況

    若 row trx_id在數組中,表示這個版本是由還沒提交的事務生成的,不可見;

    若 row trx_id不在數組中,表示這個版本是已經提交了的事務生成的,可見。

 

這裏直接最開頭3個事務分析

 

 

從圖中能夠看到,第一個有效更新是事務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,比低水位小,處於綠色區域,可見。

 

這樣執行下來,雖然期間這一行數據被修改過,可是事務A不論在何時查詢,看到這行數據的結果都是一致的,因此咱們稱之爲一致性讀。

 

一個數據版本,對於一個事務視圖來講,除了本身的更新老是可見之外,有三種狀況:

  版本未提交,不可見;

  版本已提交,可是是在視圖建立後提交的,不可見;

  版本已提交,並且是在視圖建立前提交的,可見。

 

分析獲得:

  • (1,3)還沒提交,屬於狀況1,不可見;
  • (1,2)雖然提交了,可是是在視圖數組建立以後提交的,屬於狀況2,不可見;
  • (1,1)是在視圖數組建立以前提交的,可見。

 

更新邏輯

你看圖5中,事務B的視圖數組是先生成的,以後事務C才提交,不是應該看不見(1,2)嗎,怎麼能算出(1,3)來?

 

 

 

是的,若是事務B在更新以前查詢一次數據,這個查詢返回的k的值確實是1。

 

可是,當它要去更新數據的時候,就不能再在歷史版本上更新了,不然事務C的更新就丟失了。所以,事務B此時的set k=k+1是在(1,2)的基礎上進行的操做。

 

更新數據都是先讀後寫的,而這個讀,只能讀當前的值,稱爲「當前讀」(current read)。

 

這裏咱們提到了一個概念,叫做當前讀。其實,除了update語句外,select語句若是加鎖,也是當前讀。

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

 

再往前一步,假設事務C不是立刻提交的,而是變成了下面的事務C’,會怎麼樣呢?

 

 

兩階段鎖協議

 

 

 

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

 

 

在可重複讀隔離級別下,只須要在事務開始的時候建立一致性視圖,以後事務裏的其餘查詢都共用這個一致性視圖;

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

 

 

(1,3)還沒提交,屬於狀況1,不可見;

(1,2)提交了,屬於狀況3,可見。

 

因此,這時候事務A查詢語句返回的是k=2。

顯然地,事務B查詢結果k=3。

 

小結

對於可重複讀,查詢只認可在事務啓動前就已經提交完成的數據;

 

對於讀提交,查詢只認可在語句啓動前就已經提交完成的數據;

 

問題分析

下面的表結構和初始化語句做爲試驗環境,事務隔離級別是可重複讀。如今,我要把全部「字段c和id值相等的行」的c值清零,可是卻發現了一個「詭異」的、改不掉的狀況。請你構造出這種狀況,並說明其原理。

 

 

併發條件下很容易出現更新丟失問題。

好比下面這幾種狀況。

 

 

第一個例子

因此執行順序應該是:
1, 事務A select * from t;
2, 事務B update t set c = c + 4; // 只要c或者id大於等於5就行; 固然這行也能夠和1調換, 不影響
3, 事務B commit;
4, 事務A update t set c = 0 where id = c; // 當前讀; 此時已經沒有匹配的行
5, 事務A select * from t;

 

這是典型的「丟失更新」問題。一個事務的更新操做被另一個事務的更新操做覆蓋。在RR狀態下,普通select的時候是會得到舊版本數據的,可是update的時候就檢索到最新的數據。
解決方法:在讀取的過程當中設置一個排他鎖,在 begin 事務裏, select 語句中增長 for update 後綴,這樣能夠保證別的事務在此事務完成commit前沒法操做記錄。

 

第二個例子

用新的方式來分析session B’的更新爲何對session A不可見就是:在session A視圖數組建立的瞬間,session B’是活躍的,屬於「版本未提交,不可見」這種狀況。

相關文章
相關標籤/搜索