mysql總結(二)-事務

寒滴空階發表於2020-04-05

事務隔離級別

  1. 讀未提交是指,一個事務還沒提交時,它做的變更就能被別的事務看到。
  2. 讀提交是指,一個事務提交之後,它做的變更才會被其他事務看到。
  3. 可重複讀是指,一個事務執行過程中看到的資料,總是跟這個事務在啟動時看到的資料是一致的。當然在可重複讀隔離級別下,未提交變更對其他事務也是不可見的。
  4. 序列化,顧名思義是對於同一行記錄,“寫”會加“寫鎖”,“讀”會加“讀鎖”。當出現讀寫鎖衝突的時候,後訪問的事務必須等前一個事務執行完成,才能繼續執行。
// session A 
begin;
select * from t1 where id = 1 //

//session B 
begin;
select * from t1 where id = 1
update t1 set amonut=amount+1 where id = 1

//session A 
select * from t1 where id = 1 //v1

// session B 
commit 

// session A 
select * from t1 where id = 1 //v2
commit 
select * from t1 where id = 1 //v3

複製程式碼

上面的查詢分析來分析每個事務隔離級別下v1,v2,v3不同的顯示

  1. 若隔離級別是“讀未提交”, 則 V1 的值就是 2。這時候事務 B 雖然還沒有提交,但是結果已經被 A 看到了。因此,V2、V3 也都是 2
  2. 若隔離級別是“讀提交”,則 V1 是 1,V2 的值是 2。事務 B 的更新在提交後才能被 A 看到。所以, V3 的值也是 2。
  3. 若隔離級別是“可重複讀”,則 V1、V2 是 1,V3 是 2。之所以 V2 還是 1,遵循的就是這個要求:事務在執行期間看到的資料前後必須是一致的。
  4. 若隔離級別是“序列化”,則在事務 B 執行“將 1 改成 2”的時候,會被鎖住。直到事務 A 提交後,事務 B 才可以繼續執行。所以從 A 的角度看, V1、V2 值是 1,V3 的值是 2。

事務隔離的實現

MySQL 中,實際上每條記錄在更新的時候都會同時記錄一條回滾操作。記錄上的最新值,通過回滾操作,都可以得到前一個狀態的值

假設一個值從1被按順序的改為2,3,4,在回滾日誌裡面就會有下面的記錄

read-view A -> 將2改為1 
read-view B -> 將3改為2 將4改為3 
read-view C -> 當前值4 
複製程式碼

通過不同時刻啟動不同的read-view,在檢視A,B,C裡面,這個記錄的值分別為1,2,4,同一記錄存在多個版本,就是資料庫的多版本(MVCC).對於 read-view A,要得到 1,就必須將當前值依次執行圖中所有的回滾操作得到.同時你會發現,即使現在有另外一個事務正在將 4 改成 5,這個事務跟 read-view A、B、C 對應的事務是不會衝突的

事務日誌的刪除和長事務

在不需要的時候才刪除。也就是說,系統會判斷,當沒有事務再需要用到這些回滾日誌時,回滾日誌會被刪除,當系統裡沒有比這個回滾日誌更早的 read-view 的時候就會刪除.

長事務意味著系統裡面會存在很老的事務檢視。由於這些事務隨時可能訪問資料庫裡面的任何資料,所以這個事務提交之前,資料庫裡面它可能用到的回滾記錄都必須保留,這就會導致大量佔用儲存空間.==長事務還佔用鎖資源,也可能拖垮整個庫==

事務MVCC的簡單分析

  1. 每個事務都有一個事務ID,叫做transaction id(遞增)

  2. 事務在啟動時,找到已提交的最大事務ID記為up_limit_id。

  3. 事務在更新一條語句時,比如id=1改為了id=2.會把id=1和該行之前的row trx_id寫到undo log裡,並且在資料頁上把id的值改為2,並且把修改這條語句的transactionid記在該行行頭

  4. 一個事務要檢視一條資料時,必須先用該事務的up_limit_id與該行的transaction id做比對,如果up_limit_id>=transactionid,那麼可以看.如果up_limit_id<transaction id,則只能去undo log裡去取。去undo log查詢資料的時候,也需要做比對,必須up_limit_id>transaction id,才返回資料

事務的可見性

// session A 
start transaction with consistent snapshot   //馬上啟動事務

// session B 
start transaction with consistent snapshot

// session C 
UPDATE t set k=k+1 where id =1 

// session B 
UPDATE t set k=k+1 where id =1 
SELECT k from t where id = 1  //查詢結果 3 

// session A 
SELECT k from t where id = 1 //查詢結果 1 
commit 

// session B 
comit 


複製程式碼
  1. A的查詢語句是1 因為他的查詢是在B檢視查詢之後,可重複讀的特性是在其他事務的更改對當前事務部可見
  2. B的查詢語句是3 因此B的查詢語句是在C的提交之後,在當前事務進行的更改,未提交對當前事務也是可見的,B的查詢是3是因為更新的時候需要去讀一次,而C已經進行語句提交了,因此B的更新語句先在當前事務查詢到k的值為2,再進行更新一次為3.因此==更新資料都是先讀後寫的,而這個讀,只能讀當前的值,稱為“當前讀”(current read)==

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

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

事務和鎖

上面的例子 我們將事務C也進行宣告事務,接下來看看會怎麼樣


// session A 
start transaction with consistent snapshot   //馬上啟動事務

// session B 
start transaction with consistent snapshot

// session C 
start transaction with consistent snapshot // C也啟動事務
UPDATE t set k=k+1 where id =1 

// session B 
UPDATE t set k=k+1 where id =1 
SELECT k from t where id = 1  //查詢結果 3 

// session C
commit 

// session A 
SELECT k from t where id = 1 //查詢結果 1 
commit 

// session B 
comit 

複製程式碼

相對於上個版本來說,事務B的更新的時候,事務C還未進行提交,這樣的話當前A,B的查詢結果會是什麼樣子呢?

上一篇文章中提到的“兩階段鎖協議”就要上場了。事務 C’沒提交,也就是說 (1,2) 這個版本上的寫鎖還沒釋放。而事務 B 是當前讀,必須要讀最新版本,而且必須加鎖,因此就被鎖住了,==必須等到事務 C’釋放這個鎖==,才能繼續它的當前讀

可重複讀的核心就是==一致性讀==;而事務更新資料的時候,只能用當前讀。如果當前的記錄的行鎖被其他事務佔用的話,就需要進入鎖等待。而讀提交的邏輯和可重複讀的邏輯類似,它們最主要的區別是:

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

相關文章