事務隔離級別
- 讀未提交是指,一個事務還沒提交時,它做的變更就能被別的事務看到。
- 讀提交是指,一個事務提交之後,它做的變更才會被其他事務看到。
- 可重複讀是指,一個事務執行過程中看到的資料,總是跟這個事務在啟動時看到的資料是一致的。當然在可重複讀隔離級別下,未提交變更對其他事務也是不可見的。
- 序列化,顧名思義是對於同一行記錄,“寫”會加“寫鎖”,“讀”會加“讀鎖”。當出現讀寫鎖衝突的時候,後訪問的事務必須等前一個事務執行完成,才能繼續執行。
// 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不同的顯示
- 若隔離級別是“讀未提交”, 則 V1 的值就是 2。這時候事務 B 雖然還沒有提交,但是結果已經被 A 看到了。因此,V2、V3 也都是 2
- 若隔離級別是“讀提交”,則 V1 是 1,V2 的值是 2。事務 B 的更新在提交後才能被 A 看到。所以, V3 的值也是 2。
- 若隔離級別是“可重複讀”,則 V1、V2 是 1,V3 是 2。之所以 V2 還是 1,遵循的就是這個要求:事務在執行期間看到的資料前後必須是一致的。
- 若隔離級別是“序列化”,則在事務 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的簡單分析
-
每個事務都有一個事務ID,叫做transaction id(遞增)
-
事務在啟動時,找到已提交的最大事務ID記為up_limit_id。
-
事務在更新一條語句時,比如id=1改為了id=2.會把id=1和該行之前的row trx_id寫到undo log裡,並且在資料頁上把id的值改為2,並且把修改這條語句的transactionid記在該行行頭
-
一個事務要檢視一條資料時,必須先用該事務的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
複製程式碼
- A的查詢語句是1 因為他的查詢是在B檢視查詢之後,可重複讀的特性是在其他事務的更改對當前事務部可見
- B的查詢語句是3 因此B的查詢語句是在C的提交之後,在當前事務進行的更改,未提交對當前事務也是可見的,B的查詢是3是因為更新的時候需要去讀一次,而C已經進行語句提交了,因此B的更新語句先在當前事務查詢到k的值為2,再進行更新一次為3.因此==更新資料都是先讀後寫的,而這個讀,只能讀當前的值,稱為“當前讀”(current read)==
一個資料版本,對於一個事務檢視來說,除了自己的更新總是可見以外,有三種情況
- 版本未提交,不可見;
- 版本已提交,但是是在檢視建立後提交的,不可見;
- 版本已提交,而且是在檢視建立前提交的,可見。
事務和鎖
上面的例子 我們將事務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’釋放這個鎖==,才能繼續它的當前讀
可重複讀的核心就是==一致性讀==;而事務更新資料的時候,只能用當前讀。如果當前的記錄的行鎖被其他事務佔用的話,就需要進入鎖等待。而讀提交的邏輯和可重複讀的邏輯類似,它們最主要的區別是:
- 在可重複讀隔離級別下,只需要在事務開始的時候建立一致性檢視,之後事務裡的其他查詢都共用這個一致性檢視;
- 在讀提交隔離級別下,每一個語句執行前都會重新算出一個新的檢視。