這篇文章主要介紹了MySQL MVVC多版本并發控制如何實現的相關知識,內容詳細易懂,操作簡單快捷,具有一定借鑒價值,相信大家閱讀完這篇MySQL MVVC多版本并發控制如何實現文章都會有所收獲,下面我們一起來看看吧。
MVCC(Multiversion Concurrency Control),多版本并發控制。它和undo log中的版本鏈息息相關,MVVC通過數據行的多個版本來實現數據庫的并發控制。
簡單的說就是當前事務查詢另一個事務正在更改的行(如果此時讀取就會發生臟讀),不用加鎖等待,而是讀取該數據的歷史版本,降低響應時間。
MVVC是通過undo log和Read View兩種技術實現的。
MVCC在MySQL InnoDB中的實現主要是為了提高數據庫并發性能,用更好的方式去處理讀-寫沖突,做到即使有讀寫沖突時,也能做到不加鎖,非阻塞并發讀 ,而這個讀指的就是快照讀 , 而非當前讀。當前讀實際上是一種加鎖的操作。
當前讀讀取的記錄一定是最新的數據,讀取時還要保證其他并發事務不能修改當前記錄,會對讀取的記錄進行加鎖。
加鎖的讀被稱為當前讀,還有數據的增刪改都是要先讀取數據的,這一讀取過程也是當前讀。
SELECT * FROM t LOCK IN SHARE MODE; # 共享鎖 SELECT * FROM t FOR UPDATE; # 排他鎖 UPDATE SET t..
快照讀又叫一致性讀,讀取的是數據行的快照版本。在MySQL中,普通的select語句(不加for update或lock in share mode的select語句)默認就是使用的快照讀,不加鎖。
SELECT * FROM table WHERE ...
之所以這樣,是因為快照讀可以避免加鎖操作,降低開銷。
當事務的隔離級別是串行時,快照讀就沒有用了,會退化為當前讀。
隔離級別:
在MySQL中默認的隔離級別就是可重復讀RR,可以解決不可重復讀問題,在MySQL中,特別的還額外支持解決幻讀問題。
它是如何解決幻讀問題的呢?有兩種方式:
使用間隙鎖和臨鍵鎖解決,簡而言之就是加鎖,在此期間其他事務不能夠插入數據
MVCC方式,無需加鎖,消耗低(缺點是沒有完全解決幻讀問題)。
undo log版本鏈:
對應InnoDB來說,聚簇索引中的每個記錄都包含了兩個必要的隱藏字段:
trx_id:每次一個事務對某條聚簇索引記錄進行改動時,都會把該事務的事務id賦值給trx_id隱藏列。
roll_pointer:回滾指針,每次修改數據時,都會把舊數據放入undo log日志中,新的數據指向該舊數據,做成一個版本鏈,該指針字段就稱為回滾指針,通過該指針可以找到修改前的數據。
舉例:
有一個id為8的事務創建了一條數據,那么該記錄的示意圖大概如下:
假設之后兩個id分別為10、20的事務對這條記錄進行update操作,流程如下:
事務10 | 事務20 |
---|---|
BEGIN; | |
BEGIN; | |
UPDATE student SET name='李四' WHERE id=1; | |
UPDATE student SET name='王五' WHERE id=1; | |
COMMIT; | |
UPDATE student SET name='趙六' WHERE id=1; | |
UPDATE student SET name='錢七' WHERE id=1; | |
COMMIT; |
每次修改都會生成一個undo log日志,每個日志都相互鏈接,構成版本鏈,此時該條數據的示意圖如下:
每個版本中還包含生成該版本時對應的事務id 。
有了undo log就可以讀取到記錄的歷史版本,那么在什么情況下,讀取哪個版本的記錄呢?這就用到了Read View,它幫我們解決了行的可見性問題。
Read View就是當某個事務在使用MVVC機制進行快照讀操作時產生的讀視圖。該視圖是數據庫當前所有活躍事務id(還未提交的事務)組成的列表的一個快照。
四種隔離級別里,讀未提交和串行化是不會使用MVVC的,因為讀未提交直接讀取某個數據的最新數據即可,串行化是通過加鎖來讀的。
讀已提交和可重復讀都必須保證讀到的數據都是其他事務提交了的,所以,其他事務修改了數據但是還未提交,我們不能夠訪問該數據,但可以通過MVVC機制讀取該記錄的歷史版本,核心問題就是需要判斷版本鏈中的哪條歷史版本是當前事務可見的,這也是ReadView要解決的問題。
Read View包含4個比較重要的內容:
creator_trx_id:創建這個Read View的事務id,Read View和事務是一一對應的。
只有事務對表中的記錄做修改時才會為事務分配事務id,否則一個事務中只有讀操作,該事務的id默認為0。
trx_ids:表示在生成Read View時當前系統中活躍的事務id列表。提交了的事務不在其中。
up_limit_id:活躍的事務中最小的事務id。
low_limit_id:表示生成Read View時系統應該分配給下一個事務的id值,同樣也表示系統中最大的事務id值。
注意:low_limit_id并不是trx_ids中的最大值,事務id是遞增分配的。比如,現在有id為1, 2,5這三個事務,之后id為5的事務提交了。那么一個新的讀事務在生成ReadView時, trx_ids就包括1和2,up_limit_id的值就是1,low_limit_id的值就是6。
版本鏈
當某個事務有了Read View,訪問某條記錄時,需要按照下面的步驟判斷該記錄的哪個版本可見:
如果該版本記錄的trx_id和Read View的creator_trx_id相同,意味著該版本的記錄是由當前事務修改的,因此該版本可以被當前事務訪問
如果該版本記錄的trx_id小于Read View的up_limit_id,證明當前事務生成Read View時,此事務已經提交了,所以當前事務可以讀取該版本。
如果該版本的trx_id大于等于low_limit_id,證明生成該版本的事務在當前事務生成Read View之后才開啟,所以該版本不可以被當前事務訪問。
如果被訪問版本的trx_id屬性值在ReadView的up_limit_id和low_limit_id之間,那就需要判斷一下trx_id屬性值是不是在trx_ids列表中,如果不在的話才能訪問,否則不能訪問。
了解了這些概念之后,我們來看下當查詢一條記錄的時候,系統如何通過MVCC找到它:
首先獲取事務自己的版本號,也就是事務ID;
獲取 ReadView;
查詢得到的數據,然后與 ReadView 中的事務版本號進行比較;
如果不符合 ReadView 規則,就需要從Undo Log中獲取歷史快照;
最后返回符合規則的數據。
在隔離級別為讀已提交時,一個事務中的每一次SELECT查詢都會重新獲取一次Read View,而可重復讀是第一SELECT操作才會生成Read View,之后的查詢操作復用這一個。
導致這兩種的差距是因為:可重復讀要保證一個事務中相同的SELECT讀取的內容是相同的。
COMMITTED隔離級別下
現在有兩個事務id分別為10、20的事務在執行:
-- id為10的事務 begin; update t set name='李四' where id=1; update t set name='王五' where id=1; -- id為20的事務 更新其他行的數據
此刻,表中id為1的記錄得到的版本鏈表如下所示:
此時新來一個事務執行如下操作:
begin; select * from t where id=1; -- 事務10、20未提交
查詢到的結果為張三。
具體的過程如下:
在執行select語句前,先生成一個Read View,Read View的creator_trx_id為0,trx_ids列表的內容是[10,20],up_limit_id為10,low_limit_id為21。
查詢name為王五的最新版本的記錄,按規則進行對比,因為trx_id為10,10剛好是trx_ids中的記錄,所以這條記錄對當前事務不可見,根據回滾指針得到下一個版本
下一個版本name為李四,也不行
繼續找到name為張三的版本,trx_id為8,8小于up_limit_id,所以該版本對當前事務可見,得到最終結果
接下來,再將id為10的事務進行commit提交。然后id為20的事務來更新記錄:
begin; -- id為20的事務 update t set name='趙六' where id=1; update t set name='錢七' where id=1;
此時版本鏈更新為:
再到剛才使用READ COMMITTED隔離級別的事務中繼續查找這個id 為1的記錄,得到的結果為name=王五的那條記錄。執行過程如下:
生成Read View,Read View的creator_trx_id為0,trx_ids列表的內容是[20],up_limit_id為20,low_limit_id為21。
因為前兩個版本的記錄trx_id為20,存在trx_ids中,所以跳過
到第三條記錄時,trx_id為10,小于20,可以讀取,所以最終結果為王五
注意:READ COMMITTED,每次讀取數據前都生成一個新的ReadView。
假如此時id為10的事務和id為20的事務正在修改,都未提交,修改內容和前面的一樣,但是還未提交,此時當前事務做一個查詢。
步驟為:
生成Read View,Read View的creator_trx_id為0,trx_ids列表的內容是[10,20],up_limit_id為10,low_limit_id為21。
trx_id為10和20的都不滿足要求
最后查找到name為張三的歷史版本的數據
此時,id為10的記錄提交事務。
當前事務又需要select id為1的記錄,步驟為:
因為是可重復讀,且第一次select已經生成過Read View了,所有會復用它,不重新生成。
所以trx_id為10和20的記錄依舊不符合規則,最終得到的數據還是張三,符合可重復讀的規范
注意:REPEATABLE READ,每次讀取都復用第一次生成的Read View
假設現在有一條數據,id為1
當前活躍的事務有10和20。
此時當前事務啟動了,執行如下SQL語句:
begin; select * from student where id>=1;
在開始前生成Read View,內容如下:creator_trx_id=0,trx_ids= [10,20] , up_limit_id=10, low_limit_id=21。
由于id大于等于1的數據只有一個,且該數據的trx_id為8,小于up_limit_id,所以可以讀取到。
在這之后id為10的事務新增了一行數據,增加了id為2的數據,且提交了。
此時當前線程繼續查找id>=1的數據,因為是可重復讀,復用剛剛的Read View。
得到兩行數據,但是因為id為2的數據trx_id為10,該值在Read View的trx_ids中存在,所以該記錄對當前事務不可見,所以最后查詢到的數據只有一條記錄。
如果當前事務再插入id為2的數據就插不進去,所以說MVVC只解決了一半的幻讀問題。
關于“MySQL MVVC多版本并發控制如何實現”這篇文章的內容就介紹到這里,感謝各位的閱讀!相信大家對“MySQL MVVC多版本并發控制如何實現”知識都有一定的了解,大家如果還想學習更多知識,歡迎關注億速云行業資訊頻道。
免責聲明:本站發布的內容(圖片、視頻和文字)以原創、轉載和分享為主,文章觀點不代表本網站立場,如果涉及侵權請聯系站長郵箱:is@yisu.com進行舉報,并提供相關證據,一經查實,將立刻刪除涉嫌侵權內容。