mysql如何實現四大隔離級別的?

在mvcc下,mysql中用到的鎖還是共享鎖和排他鎖么?如果是的話,那麼是怎樣結合鎖和mvcc來實現rc和rr隔離級別的呢?還有mysql中在ru隔離級別下,兩個事務同時讀取數據對象A,當事務1修改A之後,事務2可以立即讀到修改數據,但是在1提交之前,2想修改A會被阻塞,據說ru下沒有mvcc。那麼是怎麼實現出這樣的效果的呢?

以下是個人推測:

行鎖:

read uncommitted:

事務修改數據加排他鎖,改完釋放排它鎖,加共享鎖,直到提交事務釋放。

事務讀取數據讀永遠讀最高版本數據。

read committed:

事務修改數據加排它鎖,直到事務提交釋放。

事務讀取數據永遠讀最高版本數據。

repeatable read :

事務修改數據加排它鎖,直到事務提交釋放。

事務讀取數據版本號小於等於事務版本號的數據。

serializablr:

不使用mvcc。

事務修改數據加排他鎖,讀取數據加共享鎖,直到提交釋放。(低並發,極易死鎖)

這是個人根據測試結果進行猜測的。望大神指點更正!


概念

事務ID

事務ID是一個遞增的整數,唯一的標識一個事務。ID的大小可以用來表示事務的串列化順序,用於事務可見性的判斷。

多版本存儲

MySQL InnoDB實現了多版本並發控制(MVCC),在多版本存儲上,MySQL採用從新到舊(Newest To Oldest)的版本鏈。B+Tree葉結點上,始終存儲的是最新的數據(可能是還未提交的數據)。而舊版本數據,通過UNDO記錄(做DELTA)存儲在回滾段(Rollback Segment)里。每一條記錄都會維護一個ROW HEADER元信息,存儲有創建這條記錄的事務ID,一個指向UNDO記錄的指針。通過最新記錄和UNDO信息,可以還原出舊版本的記錄。

如下圖, V1被一個事務更新為V2,V2被另一個事務更新為V3,Δ1存儲V1到V2的更新,Δ2存儲V2到V3的更新。此時,如果一個事條定位到B+Tree葉子節點的記錄V3,則通過V3+Δ2可以還原出V2,通過V3+Δ2+Δ1可以還原出V1。

ReadView (或者可以稱之為Snapshot)

ReadView是某一個時間點,事務執行狀態的一個快照,可以用來判斷事務的可見性。ReadView的基本結構如下:

ReadView {
creator_trx_id
low_limit_id
up_limit_id
ids
...
}

creator_trx_id 創建這個ReadView的事務ID

low_limit_id 所有事務ID大於或等於low_limit_id對當前事務都不可見

up_limit_id 所有事務ID嚴格小於up_limit_id的事務對當前事務可見

ids 未提交的事務ID列表

可見性的判斷

事務通過用當前事務(或語句,取決於隔離級別)的RaadView來判斷一個事務id的操作是否對當前事務可見。判斷可見性的偽代碼如下:

IsVisible(trx_id)
if (trx_id == creator_trx_id) // 當前事務
return true;
else if (trx_id &< up_limit_id) // ReadView創建時, 事務已提交 return true; else if (trx_id &>= low_limit_id) // ReadView創建時,事務還未被創建
return false;
else if (trx_id is in m_ids) // ReadView創建時,事務正在執行,但未提交
return false
else // ReadView創建時, 事務已提交
return true;

不同隔離級別的實現

可串列化(Serializable)

在可串列化級別上,MySQL執行S2PL並發控制協議, 一階段申請,一階段釋放。讀寫都要加鎖。

可重複讀(Repeatable Read)

可重複讀是MySQL默認的隔離級別,理論上說應該稱作快照(Snapshot)隔離級別。讀不加鎖,只有寫才加鎖,讀寫互不阻塞,並發度相對於可串列化級別要高,但會有Write Skew異常。

事務在開始時創建一個ReadView,當讀一條記錄時,會遍歷版本鏈表,通過當前事務的ReadView判斷可見性,找到第一個對當前事務可見的版本,讀這個版本。

對於寫操作,包括Locking Read(SELECT ... FOR UPDATE), UPDATE, DELETE,需要加寫鎖。根據謂詞條件上索引使用情形,鎖定有不同的方式:

1)有索引:

對於索引上有唯一約束且為等值條件的情形,不用GAP LOCK,只鎖定索引記錄。對於其它情形,使用GAP LOCK,相當於謂詞鎖。

2)沒有索引:

由於MySQL沒有實現通用的謂詞鎖,這時就相當於鎖全表。

讀已提交(Read Committed)

MySQL的讀已提交實際是語句級別快照。

與可重複讀級別主要有兩點不同:

1)獲得ReadView的時機。每個語句開始執行時,獲得ReadView,可見性判斷是基於語句級別的ReadView。讀的策略與可重複讀類似。

2)寫鎖的使用方式。這裡不需要GAP LOCK,只使用記錄鎖。並且事務只持有被UPDATE/DELETE記錄的寫鎖(可重複讀需要保留意向寫鎖直到事務結束)。

讀未提交(Read Uncommitted)

讀最新的數據,不管這條記錄是不是已提交。不會遍歷版本鏈,少了查找可見的版本的步驟。這樣可能會導致臟讀。

對寫仍需要鎖定,策略和讀已提交類似,避免臟寫。


你的推測:

ru隔離級別:寫加X鎖,事務提交或回滾釋放X鎖,讀不加鎖

rc:寫加排它鎖,讀一條語句看到的是一個snapshoot version

rr:一個事務看到的一個snapshoot


推薦搜下 資料庫內核月報 ,想知道的幾年都有~


資料庫系統實現 你想要的都在這本書裡面


我第一想法是安利你看源碼...(逃ε=ε=ε=┏(゜ロ゜;)┛。。


推薦閱讀:

MySQL中inner join 和 cross join 的區別?
mysql如何解決評論遞歸查詢?
MySQL 和 PostgreSQL 相比,對 JSON 的支持如何?
SQL中 LEFT JOIN ON 條件的效率高低比較?
為什麼php在向mysql提交數據時變數外要用單引號?

TAG:資料庫 | SQL | MySQL | 事務並發 | 資料庫事務 |