多版本并發控制:MVCC的作用和基本原理
- 1、MVCC簡介
- 1.1 快照讀與當前讀的區別
- 1.1.1 快照讀
- 1.1.2 當前讀
- 1.2 數據庫的讀寫問題
- 1.3 MVCC的作用
- 2、MVCC實現原理之ReadView
- 2.1 什么是ReadView
- 2.2 ReadView的設計思路
- 2.3 MVCC整體操作流程
1、MVCC簡介
1.1 快照讀與當前讀的區別
mysql在讀數據的場景下,根據是否加鎖分為了2種讀的方式:
1.1.1 快照讀
不加鎖的簡單的select都屬于快照讀
,即不加鎖的非阻塞讀。比如:
SELECT * FROM students WHERE ...
之所以出現快照讀的情況,是基于提高并發性能的考慮,快照讀的實現是基于MVCC。他在很多情況下,避免了加鎖操作,降低了開銷。
1.1.2 當前讀
當前讀讀取的是記錄最新的數據。加鎖的SELECT,或者對數據進行增刪改查都會進行當前讀。比如:
SELECT * FROM student LOCK IN SHARE MODE; # 共享鎖
SELECT * FROM student FOR UPDATE; # 排他鎖
INSERT INTO student VALUES ...; # 排他鎖
DELETE FROM student WHERE ...; # 排他鎖
UPDATE student SET ...; # 排他鎖
1.2 數據庫的讀寫問題
數據庫多事務場景下的讀-讀不會存在并發問題,寫-寫場景存在并發問題,因此一定需要加鎖。
那讀-寫、或者寫-讀場景下如何處理?
方案一:讀操作也采用加鎖的方式。
比如,在銀行存款的事務中,需要先讀取賬戶余額,然后再進行存/取操作,最后寫入數據庫中。在這個過程中,是不希望其他事務在該事務還未結束的情況下訪問該余額。這種情況下,在讀取余額的時候就需要進行加鎖操作了。這樣就可以保證本次操作結束后的金額,一定是和預期一致的。
1.3 MVCC的作用
數據庫的讀寫問題,除了采用加鎖的方式解決,還可以通過MVCC的方式解決。
MVCC(Multiversion Concurrency Control)多版本并發控制
,通過數據行的多個版本管理實現數據庫的并發控制。即查詢一些正在被另一個事務更新的數據行時,可以看到他們被更新之前的值,不用等待另一個事務釋放鎖。
所謂的MVCC,就是生成一個ReadView,通過ReadView找到符合條件的記錄版本(歷史版本由undo日志構建)。查詢語句只能讀到在生成ReadView之前已提交事務做的修改,不影響其他事務進行寫操作,因此可以解決讀寫問題。相比于加鎖的方式解決讀寫問題,可以大幅提高并發性能。
2、MVCC實現原理之ReadView
MVCC的實現依賴于:隱藏字段、Undo Log、Read View
undo日志版本鏈中索引記錄都包含2個必要的隱藏列:
- trx_id:每次一個事務對某條索引記錄進行修改時,都會把該事務的事務id賦值給trx_id隱藏列。
- roll_pointer:每次對某條索引記錄進行修改時,都會把舊版本寫入到undo日志中,這個隱藏列相當于一個指針,可以通過它來找到該記錄修改前的信息。
以下圖舉例,其中藍色部分為頁面的當前記錄,綠色部分為undo日志。
2.1 什么是ReadView
多個事務對同一行記錄進行更新會產品多個歷史快照,這些歷史快照保存在undo log里。如果一個事務想要查詢這個行記錄,怎么判斷讀取哪個版本的記錄呢?這個時候就可以用上Readview了,它解決了多事務場景下的可見性的問題。
Readview就是事務在使用MVCC機制進行快照讀操作時產生的讀視圖。當事務啟動時,會生成數據庫系統當前的一個快照,Innodb為每個事務構造了一個數組,用來記錄并維護系統當前未提交事務的ID。
2.2 ReadView的設計思路
Readview要解決的核心問題是:判斷版本鏈中的哪個版本是當前事務可見的。
為此,Readview設計了4個核心字段:
- creator_trx_id:創建Readview的事務ID。只有對表做增刪改操作時才會分配事務ID,讀操作的事務ID默認值為0。
- trx_ids:表示生成Readview時,當前系統中未提交事務的事務id列表。
- up_limit_id:當前未提交事務列表中最小的事務id。
- low_limit_id:生成Readview時,系統應該分配給下一個事務的id值。
舉例:現在有id未1,2,3這3個事務,然后id為3的事務提交了。此時一個新的讀事務在生成Readview時,trx_ids的值是:[1,2],up_limit_id的值是1,low_limit_id的值是4。
有了Readview,在訪問某條記錄時,根據以下步驟判斷記錄的某個版本是否可見
- 如果被訪問版本的trx_id值與Readview中的
creator_trx_id
值相同:表明當前呢事務在訪問自己修改的記錄,所以該版本可以被當前事務訪問。 - 如果被訪問版本的trx_id值小于Readview中的
up_limit_id
值:表明生成該版本的事務在當前事務生成Readview之前就已經提交了,所以該版本可以被當前事務訪問。 - 如果被訪問版本的trx_id值大于等于Readview中的
low_limit_id
值:表明生成該版本的事務在當前事務生成Readview之后才開啟,所以該版本不可以被當前事務訪問。 - 如果被訪問版本的trx_id值在Readview中的
up_limit_id
值和low_limit_id
值之間,需要判斷一下trx_id值是否在trx_ids
列表中。
1)如果在:說明創建Readview時,生成該版本的事務還未提交,該版本不可以被訪問。
2)如果不在:說明創建Readview時,生成該版本的事務已被提交,該版本可以被訪問。
2.3 MVCC整體操作流程
查詢記錄時,MVCC操作流程如下。如果某個版本的數據對當前事務不可見的話,就順著版本鏈找下一個版本的數據,繼續按照流程進行判斷。
- 獲取事務自己的版本號,即事務ID
- 生成Readview
- 查詢數據,根據數據的版本號(trx_id)值按照2.2中的規則進行判斷
- 如果不符合2.2中Readview規則,從undo log中獲取歷史快照
- 循環執行,最后返回符合2.2中Readview規則中的數據。