介紹多版本併發控制 多版本併發控制技術(Multiversion Concurrency Control,MVCC) 技術是為瞭解決問題而生的,通過 MVCC 我們可以解決以下幾個問題: 讀寫之間阻塞的問題:通過 MVCC 可以讓讀寫互相不阻塞,即讀不阻塞寫,寫不阻塞讀,這樣就可以提升事務併發處理能 ...
介紹多版本併發控制
多版本併發控制技術(Multiversion Concurrency Control,MVCC)
技術是為瞭解決問題而生的,通過 MVCC 我們可以解決以下幾個問題:
- 讀寫之間阻塞的問題:通過 MVCC 可以讓讀寫互相不阻塞,即讀不阻塞寫,寫不阻塞讀,這樣就可以提升事務併發處理能力。
- 降低了死鎖的概率:這是因為 MVCC 沒有使用鎖,讀取數據時並不需要加鎖,對於寫操作,也只鎖定必要的行。
- 解決一致性讀的問題:一致性讀也被稱為快照讀,當我們查詢資料庫在某個時間點的快照時,只能看到這個時間點之前事務提交更新的結果,而不能看到這個時間點之後事務提交更新的結果。
MVCC 的思想
MVCC 是通過數據行的歷史版本來實現資料庫的併發控制。
簡單來說 MVCC 的思想就是保存數據的歷史版本。這樣一個事務進行查詢操作時,就可以通過比較版本號來判斷哪個較新的版本對當前事務可見。
InnoDB 對 MVCC 的實現
MVCC 沒有正式的標準,所以在不同的 DBMS 中,MVCC 的實現方式可能是不同的。
InnoDB 對 MVCC 的實現主要是通過 版本鏈 + ReadView 結構完成。
版本鏈存儲記錄的多個版本
先介紹聚簇索引記錄的隱藏列,再介紹 Undo Log 版本鏈
對於使用 InnoDB 存儲引擎的表來說,它的聚簇索引記錄中都包含 3 個隱藏列
- db_row_id:隱藏的行 ID。在沒有自定義主鍵也沒有 Unique 鍵的情況下,會使用該隱藏列作為主鍵。
- db_trx_id:操作這個數據的事務 ID,也就是最後一個對該數據進行插入或更新的事務 ID。
- db_roll_ptr:回滾指針,也就是指向這個記錄的 Undo Log 信息。Undo Log 中存儲了回滾需要的數據。
事務ID
事務執行過程中,只有在第一次真正修改記錄時(比如進行 insert、delete、update 操作),才會被分配一個唯一的、單調遞增的事務 ID,如果沒有修改記錄操作,按照一定的策略分配一個比較大的事務 ID,減少分配事務 ID 的鎖競爭。每當事務向資料庫寫入新內容時, 所寫的數據都會被標記操作所屬的事務的事務ID。
在 InnoDB 存儲引擎中,版本鏈由數據行的 Undo Log 組成。
每次對數據行進行修改,都會將舊值記錄到 Undo Log,算是該數據行的一個舊版本。
Undo Log 有兩個重要的屬性:db_roll_ptr、db_trx_id
-
Undo Log 也有一個 db_roll_ptr 屬性(insert 操作對應的 Undo Log 沒有 db_roll_ptr 屬性,因為 insert 操作對應的數據行沒有更早的版本),Undo Log 的 db_roll_ptr 屬性指向上一次操作的 Undo Log,所有的版本被 db_roll_ptr 屬性連接形成一個鏈表。該鏈表即版本鏈,版本鏈的頭節點就是數據行的最新值。
-
Undo Log 還包含生成該版本時,對應的事務 ID,用於判斷當前版本的數據對事務的可見性。
版本鏈如下圖所示。這樣如果我們想要查找歷史快照,就可以通過遍歷回滾指針的方式進行查找。
ReadView 判斷版本鏈中的哪個較新的版本對當前事務是可見的
ReadView 用來判斷版本鏈中的哪個較新的版本對當前事務是可見的。
ReadView 中主要包含 4 個比較重要的屬性:
- m_ids:表示在生成 ReadView 時,當前系統中所有活躍的讀寫事務的 ID 集合(列表)
- min_transaction_id:表示在生成 ReadView 時,m_ids 中的最小值
- max_transaction_id:表示在生成 ReadView 時,系統應該分配給下一個事務的 ID 值
- creator_transaction_id:表示生成該 ReadView 的事務的 ID
有了這個 ReadView,這樣在訪問某條記錄時,就可以用 ReadView 來判斷版本鏈中的哪個較新的版本對當前事務是可見的。
-
如果被訪問版本的 transaction_id 屬性值與 ReadView 中的 creator_trx_id 值相同,表明當前事務在訪問它自己修改過的記錄,所以該版本可以被當前事務訪問。
-
如果被訪問版本的 transaction_id 屬性值 小於 ReadView 中的 min_trx_id 值,表明生成該版本的事務在當前事務生成 ReadView 前已經提交了,所以該版本可以被當前事務訪問。
-
如果被訪問版本的 transaction_id 屬性值 大於 ReadView 中的 max_trx_id 值,表明生成該版本的事務在當前事務生成 ReadView 後才開啟,所以該版本不可以被當前事務訪問。
-
如果被訪問版本的 transaction_id 屬性值在 ReadView 的 min_trx_id 和 max_trx_id 之間,那就需要判斷一下 transaction_id 屬性值是不是在 m_ids 列表中:
- 如果在,表明生成 ReadView 時,被訪問版本的事務還是活躍的,所以該版本不可以被當前事務訪問
- 如果不在,表明生成 ReadView 時,被訪問版本的事務已經被提交了,所以該版本可以被當前事務訪問
如果某個版本的數據對當前事務不可見的話,那就順著版本鏈找到下一個版本的數據,繼續按照上邊的步驟判斷
可見性,依此類推,直到版本鏈中的最後一個版本。如果最後一個版本也不可見的話,那麼就意味著該條記錄對當前事務完全不可見,查詢結果就不包含該記錄。
ReadView 的生成時機
MVCC 可以防止臟讀,也可以防止不可重覆讀。
防止臟讀 和 防止不可重覆讀 實現的不同之處就在:ReadView 的生成時機不同
- 防止臟讀:每次讀取數據前,都生成一個 ReadView
- 防止不可重覆讀:在當前事務第一次讀取數據時,生成一個 ReadView,之後的查詢操作都重覆使用這個 ReadView
對於隔離級別為 讀未提交 的事務來說,直接讀取記錄的最新版本即可。
對於隔離級別為 串列化 的事務來說,InnoDB 存儲引擎使用加鎖的方式來訪問記錄。
對於隔離級別為 讀已提交 和 可重覆讀 的事務來說,都必須保證只能讀到已經提交的事務修改的數據,不能讀到未提交的事務修改的數據。
參考資料
MySQL 是怎樣運行的:從根兒上理解 MySQL - 小孩子4919 - 掘金課程 (juejin.cn)
本文來自博客園,作者:真正的飛魚,轉載請註明原文鏈接:https://www.cnblogs.com/feiyu2/p/mvcc.html