數據庫隔離級別實現原理及鎖介紹

一、事務隔離級別介紹

衆所周知,MySQL 數據庫事務的隔離級別有4個,由低到高依次爲 Read Uncommitted(讀未提交)、Read Committed(讀已提交)、Repeatable Read(可重複讀)、Serializable(串行化),這四個級別可以逐個解決髒讀、不可重複讀、幻象讀這幾類問題。MySQL 默認的隔離級別是可重複讀。

事務隔離級別 髒讀 不可重複讀 幻讀
未提交讀
提交讀 ×
可重複讀 × ×
串行化 × × ×

那麼 MySQL 是如何實現幾種隔離級別的呢?

二、各級別的實現原理

讀未提交(RU)

在 RU 級別中,事務讀到的所有數據都是最新的數據,可能是事務提交後的數據,也可能是事務執行中的數據(可能會被回滾)。

當隔離級別爲 RU 時:

  • 所有的讀不加鎖,讀到的數據都是最新的數據,性能最好。
  • 所有的寫加行級鎖,寫完釋放。

讀已提交(RC)

使用 MVCC(多版本併發控制) 技術,在每一行加入隱藏的字段(DB_TRX_ID:修改該行的最後一個事務的id,DB_ROLL_PTR:指向當前行的undo log日誌,DB_ROW_ID:行標識,DELETE_BIT:刪除標誌),它實現了不加鎖的讀操作。

當隔離級別爲RC時:

  • 寫操作:加行級鎖。事務開始後,會在 UNDO 日誌中寫入修改記錄,數據行中的隱藏列 DATA_POLL_PTR 存儲指向該行 UNDO 記錄的指針。
  • 讀操作:不加鎖。在讀取時,如果該行被其它事務鎖定,則順着隱藏列 DATA_POLL_PTR 指針,找到上一個有效的歷史記錄(有效的記錄:該記錄對當前事務可見,且 DELETE_BIT=0 )。

可重複讀(RR)

使用MVCC技術來實現不加鎖的讀操作。

當隔離級別爲RR時:

  • 寫操作:加行級鎖。事務開始後,會在 UNDO 日誌中寫入修改記錄,數據行中的隱藏列 DATA_POLL_PTR 存儲指向該行的 UNDO 記錄的指針。
  • 讀操作:不加鎖。在讀取時,如果該行被其它事務鎖定,則順着隱藏列 DATA_POLL_PTR 指針,找到上一個有效的歷史記錄(有效的記錄:該記錄對當前事務可見,且 DELETE_BIT=0 )。

從上面可以知道:實際上RC和RR級別的操作基本相同,而不同之處在於:行記錄對於當前事務的可見性(可見性:即哪個版本的行記錄對這個事務是可見的)。RC級別對數據的可見性是該數據的最新記錄,RR基本對數據的可見性是事務開始時,該數據的記錄。

1) 行記錄的可見性(read_view)的實現

在 innodb 中,創建一個事務的時候,會將當前系統中的活躍事務列表創建一個副本(read_view),裏面存儲着的都是在當前事務開始時,還沒commit的事務,這些事務裏的值對當前事務不可見。read_view中有兩個關鍵值 up_limit_id(當前未提交事務的最小版本號 -1,在 up_limit_id 之前的事務都已經提交,在 up_limit_id 之後的事務可能提交,可能還沒提交) 和 low_limit_id(當前系統尚未分配的下一個事務id,也就是目前已出現過的事務 id 的最大值 +1 。注意: low_limit_id 不是最大的活躍事務的 id 。)

注意:當前事務和正在 commit 的事務是不在 read_view 中的。

2)無論是RC級別還是RR級別,其判斷行記錄的可見性的邏輯是一樣的。

當該事務要讀取 undo 日誌中的行記錄時,會將行記錄的版本號(DB_TRX_ID)與 read_view 進行比較:

  1. 如果 DB_TRX_ID 小於 up_limit_id,表示該行記錄在當前事務開始前就已經提交了,並且DELETE_BIT=0,則該行記錄對當前事務是可見的。

  2. 如果 DB_TRX_ID 大於 low_limit_id,表示該行記錄在所在的事務在本次事務創建後才啓動的,所以該行記錄的當前值不可見。

  3. 如果 up_limit_id <= DB_TRX_ID <= low_limit_id,判斷 DB_TRX_ID 是否在活躍事務鏈中,如果在就是不可見,如果不在就是可見的。

  4. 如果上面判斷都是不可見的,則讀取 undo 日誌中該行記錄的上一條行記錄,繼續進行判斷。

而對於 RC 級別的語句級快照和 RR 級別的事務級快照的之間的區別,其實是由 read_view 生成的時機來實現的。RC 級別在執行語句時,會先關閉原來的 read_view,重新生成新的 read_view。而 RR級別的 read_view 則只在事務開始時創建的。所以 RU 級別每次獲取到的都是最新的數據,而RR級別獲取到的是事務開始時的數據。

3)值得注意的是:在上面的可見性判斷中,雖然邏輯是一樣的,但是實際意義上是有區別的:

在第二步中,對於 RC 級別來說,low_limit_id 是執行語句時已出現的最大事務id+1,可以認爲在執行語句時,是不存在事務會比low_limit_id 要大,所以大於 low_limit_id 的事務都是不可見的。而對於 RR 級別來說,low_limit_id 是當前事務開始時已出現的最大事務+1(也可以認爲是當前事務的id+1,因爲在創建當前事務時,當前事務的id最大),大於 low_limit_id 的事務表示是在該事務開始後創建的,所以對RR級別是不可見。

在第三步中,對於 RC 級別來說,只要 DB_TRX_ID 不在活躍鏈表中,則無論 DB_TRX_ID 是否大於事務 id,RC 都是可見的。而對於 RR 級別來說,因爲 low_limit_id 就是**當前事務 **id + 1,可以認爲小於 low_limit_id 的事務都是在當前事務創建前出現的,所以也只需要簡單判斷 DB_TRX_ID 是否在活躍鏈表中。

串行化:讀寫都會加鎖。

三、MySQL中的鎖介紹

鎖分類

Mysql爲了解決事務併發,數據安全問題,使用了鎖機制。

按照鎖的粒度可以把鎖分爲表級鎖和行級鎖

1表級鎖

Mysql中粒度最大的一種鎖,會鎖住當前操作的整張表併發性能低,但表鎖的實現簡單,耗費資源少,加鎖快,不會出現死鎖

2行級鎖

Mysql中粒度最小的一種鎖,只會鎖住當前操作的數據行。行鎖極大地提高了Mysql的併發性能,但行鎖的開銷較大,速度較慢,會出現死鎖

按照鎖的性質可以把鎖分爲共享鎖和排它鎖

1共享鎖(S鎖)

其他事務可以讀取被共享鎖鎖住的數據行,不能修改該數據行,並且也只能對該數據行加共享鎖,而不能加排它鎖。

2排它鎖(X鎖)

當一個事務對數據行加上排他鎖,那麼該事務可以讀取和修改該數據行,而其他事務不允許對該數據行加任何鎖。

表鎖

Mysql中表鎖除了共享鎖和排他鎖之外,還存在着兩種鎖:意向共享鎖(IS),意向排他鎖(IX)。

意向鎖的作用是表明該事務想對該表加一個共享/排他鎖,但並沒有真正把鎖加上去。比如,當事務想對一個被排他鎖鎖住的表加上共享鎖/排他鎖時,必須先在該表上添加一個意向共享鎖/意向排他鎖,直到鎖住表的排他鎖被釋放。

事務在給一個數據行加共享鎖前必須先取得該表的IS,在加排它鎖前必須先取得該鎖的IX。並且意向共享鎖可以同時並存多個,但是意向排他鎖同時只能有一個存在

行鎖

InnoDB行鎖是通過給索引上的索引項加鎖來實現的,這意味着:只有通過索引條件檢索數據,InnoDB才使用行級鎖,否則,InnoDB將使用表鎖。

Innodb中的行級鎖有以下幾種:

1Record Lock: 對索引項加鎖,鎖定符合條件的行。其他事務不能修改和刪除加鎖項;

2Gap Lock: 對符合條件範圍的“間隙”加鎖,鎖定記錄的範圍,不包含索引項本身。其他事務不能在鎖範圍內插入數據。“間隙(GAP)”是指鍵值在條件範圍內但並不存在的記錄。

3Next-key Lock鎖定索引項本身和間隙。Record Lock和Gap Lock的結合,Next-key Lock就是我們所說的間隙鎖,可解決幻讀問題。

舉例來說,假如emp表中只有101條記錄,其empid的值分別是 1,2,...,100,101,下面的SQL:

Select * from emp where empid > 100 for update;

是一個範圍條件的檢索**,InnoDB 不僅會對符合條件的 **empid值爲 101 的記錄加鎖,也會對 empid 大於 101 (這些記錄並不存在)的“間隙”加鎖。

在使用範圍條件檢索並鎖定記錄時,InnoDB這種加鎖機制會阻塞符合條件範圍內鍵值的併發插入。

注意

1) InnoDB 存儲引擎在 REPEATABLE-READ(可重讀) 事務隔離級別下使用的是Next-Key Lock 鎖,解決的幻讀的問題,因此InnoDB 引擎的默認的隔離級別 REPEATABLE-READ(可重讀) 已經達到了SQL標準的 SERIALIZABLE(可串行化) 隔離級別,並且事務不需要串行化。

2) 當查詢的索引含有唯一屬性時,將next-key lock降級爲record key。

3) Innodb中行級鎖是加在索引上,所以只有使用索引時,纔會加行鎖,否則只會加表鎖。但這並不意味着,只要使用了索引就會加行級鎖,如果MySQL認爲全表掃描效率更高,比如對一些很小的表或者索引範圍包括大部分表數據,它就不會使用索引,這種情況下InnoDB將使用表鎖,而不是行鎖。

死鎖

不同於MyISAM總是一次性獲得所需的全部鎖,InnoDB的鎖是逐步獲得的,當兩個事務都需要獲得對方持有的鎖,導致雙方都在等待,這就產生了死鎖。 發生死鎖後,InnoDB一般都可以檢測到,並使一個事務釋放鎖回退,另一個則可以獲取鎖完成事務,我們可以採取以上方式避免死鎖:

1) 通過表級鎖來減少死鎖產生的概率;

2) 多個程序儘量約定以相同的順序訪問表;

3 )同一個事務儘可能做到一次鎖定所需要的所有資源。

原文:https://blog.csdn.net/qecode/article/details/97274409

發表評論
所有評論
還沒有人評論,想成為第一個評論的人麼? 請在上方評論欄輸入並且點擊發布.
相關文章