InnoDB 中四種事務隔離級別是如何實現的?

2021-09-25 11:38:46 字數 4122 閱讀 5468

之前在《mysql事務詳解》中講解了事務的概念、事務引發的問題以及通過不同的隔離級別避免這些問題。

《最詳細的mysql鎖(悲觀鎖 樂觀鎖 共享鎖 排它鎖等)講解》 講解了各種鎖(包括下面會提到的記錄鎖,臨鍵鎖)。

事務併發引起的問題:丟失更新、不可重複度、髒讀和幻讀。

這裡就來講講innodb引擎中四種隔離級別是怎麼實現的?

innodb使用不同的鎖策略(locking strategy)以及mvcc機制來實現不同的隔離級別。

原理

事務在讀資料的時候不對資料加鎖。

事務在修改資料的時候只對資料增加行級共享鎖。

現象

事務1讀取某行記錄時,事務2也能對這行記錄進行讀取、更新(因為事務一並未對資料增加任何鎖)。

當事務2對該記錄進行更新時,事務1再次讀取該記錄,能讀到事務2對該記錄的修改版本(因為事務二只增加了共享讀鎖,事務一可以再增加共享讀鎖讀取資料),即使該修改尚未被提交。

事務1更新某行記錄時,事務2不能對這行記錄做更新,直到事務1結束。(因為事務一對資料增加了共享讀鎖,事務二不能增加排他寫鎖進行資料的修改)。

原理

普通讀是快照讀,這是一種不加鎖的一致性讀,底層使用mvcc實現。

加鎖的select, update, delete等語句,除了在外鍵約束檢查(foreign-key constraint checking)以及重複鍵檢查(duplicate-key checking)時會封鎖區間,其他時刻都只使用記錄鎖

原理

普通的select使用快照讀。

加鎖的select(select…in share mode/select…for update),update,delete等語句,它們的鎖,依賴於它們是否在唯一索引上使用了唯一的查詢條件,或者範圍查詢條件:

表t有個字段a,由1、2、5這三個值組成,如事務a執行select * from t where a>2 for update;得到5。若此時事務b插入了4這個值(在資料庫允許的情況下),在執行事務a中的select,會得到4和5。預設情況下事務b的操作會被阻塞,因為在這種隔離級別下,innodb採用臨鍵鎖鎖住了2到正無窮這個範圍,因此在這個範圍內的插入操作都會被阻塞。但是注意如果事務a用的是select * from t where a>2 ;則事務b的插入操作不會被阻塞。

原理

這種事務隔離級別下,所有select語句會被隱式的轉化為select…in share mode.

這會導致,如果有未提交的事務正在修改某些行,所有讀取這些行的操作都會被阻塞。

這是一致性最好,併發性最差的隔離級別。

mvcc即多版本併發控制,為了實現更好的併發,可以使得快照讀操作不用加鎖。

innodb實現了行鎖、表鎖、一致性非鎖定讀,其中一致性非鎖定讀就是通過mvcc實現的。如果當前讀取的行正在執行delete或者update操作,讀操作不會因此去等待鎖的釋放,而是通過讀取行的快照資料實現一致性讀。

下圖展示了innodb儲存引擎非鎖定的一致性讀:

從上如可以看出,每行記錄可能有多個版本(提交一次事務得到乙個版本),快照資料就是當前行資料之前的歷史版本。乙個行記錄可能有不止乙個快照資料,一般稱這種技術為行多版本控制,由此帶來的併發控制,稱之為多版本併發控制。

mvcc 只在 read commited 和 repeatable read 這兩個事務隔離性級別中使用。這是因為mvcc 和其他兩個不相容,read uncommited 總是讀取資料表最新的資料,而seriablizable則會對每乙個讀都加共享鎖。 但是 read commited 和 repeatable read對於快照資料的定義不同,read commited 隔離級別下,對於快照資料,非一致性讀總是讀取被鎖定的行最新乙份快照資料。而在repeatable read隔離級別下,對於快照資料,非一致性讀總是讀取事務開始時的行資料版本,保證了可重複讀。

在innodb中,會在每行資料後新增兩個額外的隱藏的值來實現mvcc,這兩個值乙個記錄這行資料何時被建立,另外乙個記錄這行資料何時過期(即何時被刪除)。 在實際操作中,儲存的並不是時間,而是系統的版本號,每開啟乙個新事務,系統的版本號就會遞增。

通過mvcc,雖然每行記錄都需要額外的儲存空間,更多的行檢查工作以及一些額外的維護工作,但可以減少鎖的使用,大多數讀操作都不用加鎖,讀資料操作很簡單,效能很好,並且也能保證只會讀取到符合標準的行,也只鎖住必要行。

select (不加鎖)滿足兩個條件的結果才會被返回:

建立版本號<= 當前事務版本號,小於意味著在該事務之前沒有其他事務對其進行修改,等於意味著事務自身對其進行了修改;

刪除版本號 > 當前事務版本號 ,意味著刪除操作是在當前事務之後進行的,或者刪除版本未定義,意味著這一行只是進行了插入,還沒有刪除過。

insert:為新插入的每一行儲存當前事務的版本號作為建立版本號

delete:為刪除的行儲存當前事務的版本號為刪除版本號

update:為修改的每一行儲存當前事務的版本號作為建立版本號

mysql中的讀,和事務隔離級別中的讀,是不一樣的, 在repeatable read 級別中,通過mvcc機制,雖然讓資料變得可重複讀,但我們讀到的資料可能是歷史資料,是不及時的資料(儲存在快取等地方的資料),不是資料庫當前的資料!這也就是《mysql事務詳解》中提到的兩個事務查詢同一種表為空,第乙個事務向表中插入主鍵id為1的資料,第二個事務再次查詢該錶還是為空,但是插入主鍵id為1的資料時出現主鍵重複的報錯。

對於這種讀取歷史資料(快取資料)的方式,我們叫它快照讀 (snapshot read),而讀取資料庫當前版本資料的方式,叫當前讀 (current read)。很顯然,在mvcc中:

快照讀:讀取快照資料,快照資料是指改行的之前版本的資料,該實現是通過undo段來完成。而undo用來在事務中回滾資料,因此快照資料本身沒有額外開銷。此外,快照讀不需要上鎖,因為沒有事務會對歷史的資料進行修改。

當前讀:插入/更新/刪除操作屬於當前讀,處理的都是當前真實的資料,需要加鎖。

事務的隔離級別實際上都是定義了當前讀的級別,mysql為了減少鎖處理(包括等待其它鎖)的時間,提公升併發能力,引入了快照讀的概念,使得select不用加鎖。而update、insert這些「當前讀」,就需要另外的模組來解決了。這是因為update、insert的時候肯定要讀取資料庫中的值來與當前事務要寫入的值進行對比,看看在該事務所處理的資料在這一段時間內有沒有被其他事務所操作(就是先讀取資料庫中資料的版本號與當前的版本號做檢查)。

開始事務a,執行select語句,開始事務b,對id=1的資料進行update操作。

事務a:

事務b:

事務a繼續執行select語句:

這是無論在讀已提交還是可重複讀隔離級別下,結果都是一樣的。

接著,提交事務b:

這是產生了乙個新的快照資料,在讀已提交隔離級別下得到結果:

在可重複度隔離界別下得到結果:

innodb 四種隔離級別

read uncommitted,read committed,repeatable read,serializable.預設隔離級別是repeatable read 特性1.一致性讀的方式是在第一次讀時建立快照 特性2.在鎖定讀 update delete時,加鎖方式依賴於查詢的方式,即是否是根據...

InnoDB的四種隔離級別

google所得 總結 併發事務會互相受影響,可能會導致事務產生 為了解決此類問題,innodb實現了sql92的四種標準 讀未提交 select未加鎖,可能會產生髒讀 一致性最差,併發性最差 讀提交 rc 普通select快照讀,對select insert update使用記錄鎖,可能會產生不可...

事務四種隔離級別

1.讀取未提交 乙個事務可以讀取另乙個未提交的事務的資料。髒讀 2.讀取已提交 事務a多次讀取同一資料,事務b在事務a多次讀取的過程中,對資料做了更新並提交,導致事務a多次讀取同一資料時,結果不一致。不可重複度 對應update操作 3.可重複讀 開始讀取資料時 事務開啟時 不在允許修改操作。可能會...