Mysql MVCC原理和幻讀解決

2022-09-08 07:06:11 字數 4376 閱讀 7118

reference:

reference:

快照讀:顧名思義,就是讀取的是快照資料,不加鎖的普通select都是快照讀

當前讀:就是讀取最新資料,而不是歷史資料,或者說不是快照資料,是加鎖的select,或者對資料進行正刪改都會進行當前讀。

實現原理主要是版本鏈。undo日誌、readview來實現的。

innodb 儲存引擎,表中的聚簇索引都包含三個隱藏列(row_id、trx_id、roll_pointer)。

row_id:建立的表中有主鍵或者非null的unique鍵時都不會包含row_id列。

trx_id:事務id,每次乙個事務對某條聚簇索引記錄進行改動時,都會把該事務的事務id賦值給trx_id隱藏列。

roll_pointer:回滾指標,每次對某條聚簇索引記錄進行改動時,都會把舊的版本寫入到undo日誌中,然後這個隱藏列就相當於乙個指標,可以通過它來找到該記錄修改前的資訊。

版本鏈:每次對記錄進行改動,都會記錄一條 undo 日誌,每條 undo 日誌也都有乙個 roll_pointer 屬性(insert 操作對應的 undo 日誌沒有該屬性,因為該記錄並沒有更早的版本),可以將這些 undo 日誌都連起來,串成乙個鍊錶。版本鏈的頭節點就是當前記錄最新的值。

undo log 主要用於記錄資料被修改之前的日誌,在表資訊修改之前先會把資料拷貝到undo log裡。當事務進行回滾時可以通過 undo log 裡的日誌進行資料還原

用途

類別:

代表事務在insert新記錄時產生的undo log , 只在事務回滾時需要,並且在事務提交後可以被立即丟棄。

事務在進行 update 或 delete 時產生的 undo log, 不僅在事務回滾時需要,在快照讀時也需要。所以不能隨便刪除,只有在快速讀或事務回滾不涉及該日誌時,對應的日誌才會被 purge 執行緒統一清除。

改動的記錄都存在在 undo 日誌中,那如果乙個日誌需要查詢行記錄,需要讀取哪個版本的行記錄呢?

核心問題就是:read committedrepeatable read隔離級別在不可重複讀和幻讀上的區別在**?這兩種隔離級別對應的不可重複讀幻讀問題都是指同乙個事務在兩次讀取記錄時出現不一致的情況,這兩種隔離級別關鍵是需要判斷版本鏈中的哪個版本是當前事務可見的。

readview 就是用來解決這個問題的,可以幫助我們解決可見性問題。 事務進行快照讀操作的時候就會產生 read view,它儲存了當前事務開啟時所有活躍的事務列表(這裡的活躍指的是未提交的事務。)

每乙個事務在啟動時,都會生成乙個 readview,用來記錄一些內容,readview 中主要包含 4 個比較重要的屬性:

m_ids:事務id列表,生成 readview 時當前系統中活躍的讀寫事務的事務 id 列表。

min_trx_id:最小事務id,生成 readview 時當前系統中活躍的讀寫事務中最小的事務 id 也就是 m_ids 中的最小值。

max_trx_id:下乙個事務id,生成 readview 時系統中應該分配給下乙個事務的 id 值。

creator_trx_id:當前readview所屬事務id,生成該 readview 的事務的事務 id,指定當前的 readview 屬於哪個事務。

其中,max_trx_id並不是指m_ids中的最大值,因為事務 id 是遞增分配的,假如現在有 id 為 1,2,3 這三個事務,之後 id 為 3 的事務提交了。那麼乙個新的讀事務在生成 readview 時,m_ids 就包括 1 和 2,min_trx_id 的值就是 1,max_trx_id 的值就是 4。

再有了 readview 之後,在訪問某條記錄時,只需要按照下邊的步驟判斷記錄的某個版本是否可見:

在 mysql 中,read committed 和 repeatable read 隔離級別的的乙個非常大的區別就是它們生成 readview 的時機不同

幻讀是乙個事務按照某個相同條件多次讀取記錄時,後讀取時讀到了之前沒有讀到的記錄,而這個記錄來自另乙個事務新增的新記錄,也就是說幻讀是指新插入的行

在 repeatable read 隔離級別下,事務 a 第一次執行普通的 select 語句時生成了乙個 readview(且在 rr 下只會生成乙個 rv),之後事務 b 向 user 表中新插入一條記錄並提交。

readview 並不能阻止事務 a 執行 update 或者 delete 語句來改動這個新插入的記錄(由於事務 b 已經提交,因此改動該記錄並不會造成阻塞),但是這樣一來,這條新記錄的 trx_id 隱藏列的值就變成了事務 a 的事務 id。之後 a 再使用普通的 select 語句去查詢這條記錄時就可以看到這條記錄了,也就可以把這條記錄返回給客戶端。

因為這個特殊現象的存在,我們也可以認為mvcc 並不能完全禁止幻讀

我們知道資料庫的讀操作分為當前讀快照讀,而在 rr 隔離級別下,mvcc 解決了在快照讀的情況下的幻讀,而在實際場景中,我們可能需要讀取實時的資料,比如在銀行業務等特殊場景下,必須是需要讀取到實時的資料,此時就不能快照讀。

毫無疑問,在併發場景下,我們可以通過加鎖的方式來實現當前讀,而在 mysql 中則是通過next-key locks來解決幻讀的問題。(關於 mysql 中的鎖的介紹可以看看這篇文章:一文了解 mysql 中的鎖)。

next-key locks包含兩部分:記錄鎖(行鎖,record lock),間隙鎖(gap locks)。記錄鎖是加在索引上的鎖,間隙鎖是加在索引之間的

record lock 鎖住的永遠是索引,不包括記錄本身,即使該錶上沒有任何索引,那麼innodb會在後台建立乙個隱藏的聚集主鍵索引,那麼鎖住的就是這個隱藏的聚集主鍵索引。

記錄鎖是有 s 鎖和 x 鎖之分的,當乙個事務獲取了一條記錄的 s 型記錄鎖後,其他事務也可以繼續獲取該記錄的 s 型記錄鎖,但不可以繼續獲取 x 型記錄鎖;當乙個事務獲取了一條記錄的 x 型記錄鎖後,其他事務既不可以繼續獲取該記錄的 s 型記錄鎖,也不可以繼續獲取 x 型記錄鎖。

mysql 在 repeatable read 隔離級別下是可以解決幻讀問題的,解決方案有兩種。

可以使用 mvcc 方案解決

也可以採用加鎖方案解決(間隙鎖)。

但是在使用加鎖方案解決時有問題,就是事務在第一次執行讀取操作時,那些幻影記錄尚不存在,我們無法給這些幻影記錄加上記錄鎖。所以我們可以使用間隙鎖對其上鎖。

索引對間隙鎖會產生什麼影響

對主鍵或唯一索引,如果當前讀時,where 條件全部精確命中(=或in),這種場景本身就不會出現幻讀,所以只會加行鎖,也就是說間隙鎖會退化為行鎖(記錄鎖)。

非唯一索引列,如果 where 條件部分命中(>、<、like等)或者全未命中,則會加附近間隙鎖。例如,某錶資料如下,非唯一索引2,6,9,9,11,15。如下語句要操作非唯一索引列 9 的資料,間隙鎖將會鎖定的列是(6,11],該區間內無法插入資料。

對於沒有索引的列,當前讀操作時,會加全表間隙鎖,生產環境要注意。

next-key locks 是索引記錄上的行鎖索引記錄之前的間隙鎖的組合,包括記錄本身,每個 next-key locks 是前開後閉區間(同樣說明鎖住的範圍更大,影響併發度),也就是說間隙鎖只是鎖的間隙,沒有鎖住記錄行,next-key locks 就是間隙鎖基礎上鎖住右邊界行資料

對於可重複讀預設使用的就是next key lock,但是對於「唯一索引」,比如主鍵的索引,next key lock會降級成行鎖,而不會鎖住乙個區間。因此,如果上面的事務1的update使用的是主鍵,事務2也使用主鍵進行插入,那麼實際上事務2根本不會被阻塞,可以立即插入並返回。而對於非唯一索引,next key lock則不會降級。

mysql MVCC 間隙鎖解決幻讀理解

mysql的隔離級別?讀未提交 讀提交 可重複讀 序列化 innodb預設級別為可重複讀,可重複讀會產生問題 就是幻讀。什麼是幻讀?不可重複讀側重於update這種操作,同一條資料前後讀起來不一樣的情況,幻讀側重於insert delete這種操作,前後兩次select 資料的數量會發生變化 舉個例...

InnoDB解決幻讀方法和原理

首先說結論,在rr的隔離級別下,innodb使用mvcc和next key locks解決幻讀,mvcc解決的是普通讀 快照讀 的幻讀,next key locks解決的是當前讀情況下的幻讀。讀取歷史資料的方式,我們叫它快照讀 snapshot read 而讀取資料庫當前版本資料的方式,叫當前讀 c...

髒讀 不可重複讀和幻讀

髒讀 a事務讀取b事務尚未提交的更改資料,並在這個資料的基礎上進行操作,這時候如果事務b回滾,那麼a事務讀到的資料是不被承認的。不可重複讀 指a事務讀取了b事務已經提交的更改資料。幻讀 a事務讀取b事務提交的新增資料,會引發幻讀問題。對於這兩種問題解決採用不同的辦法,防止讀到更改資料,只需對操作的資...