什么是MVCC,一文搞懂MySQL的MVCC机制
MVCC是什么
MVCC,即Multi-Version Concurrency Control (多版本并發控制)。它是一種并發控制的方法,一般在數據庫管理系統中,實現對數據庫的并發訪問,在編程語言中實現事務內存。
數據庫中同時存在多個版本的數據,并不是整個數據庫的多個版本,而是某一條記錄的多個版本同時存在,在某個事務對其進行操作的時候,需要查看這一條記錄的隱藏列事務版本id,比對事務id并根據事物隔離級別去判斷讀取哪個版本的數據。
數據庫隔離級別讀已提交、可重復讀 都是基于MVCC實現的,相對于加鎖簡單粗暴的方式,它用更好的方式去處理讀寫沖突,能有效提高數據庫并發性能。
我們先回顧一下事務的相關概念,以便更好的理解mvcc的實現原理
MySQL的事務
redo log
redo log重做日志,是記錄物理數據變化的日志,使用數據庫DML對數據的修改操作,都會產生redo log,可以保證事務的持久性
undo log
undo log是回滾日志,有兩個作用:提供回滾操作和多行版本控制(MVCC)
在數據修改的時候,不僅記錄了redo,還記錄了相對應的undo,如果因為某些原因導致事務失敗或回滾了,可以借助undo進行回滾,
undo log和redo log記錄物理日志不一樣,undo log主要記錄的是數據的邏輯變化,它是邏輯日志
可以認為執行insert時會對應在undo log中記錄一條delete語句,并且會記錄這個版本的事務id(txid),執行update語句會有一條update語句來使之數據恢復到上個版本。
當執行rollback時,就可以從undo log中的邏輯記錄讀取到相應的內容并進行回滾
多行版本控制的時候,也是通過undo log來實現的
當讀取的某一行被其它事務鎖定時,它可以從undo log中分析出該行記錄以前的數據是什么。從而提供該行版本信息。
并且undo log的內容變更也會記錄到redo log中,從而實現undo log的持久化
總而言之就是undo log提供了一種數據庫快照的功能,通過事務id和undo log我們可以找到歷史版本的數據
MySQL事務的隔離級別
SQL標準中定義了四個隔離級別:
- READ-UNCOMMITTED(讀取未提交):最低的隔離級別,允許讀取尚未提交的數據變更,可能導致臟讀、幻讀、不可重復讀;
- READ-COMMITTED(讀取已提交):允許讀取并發事務已經提交的數據,可以阻止臟讀,但是幻讀或不可重復讀任有可能發生
- REPAATABLE-READ(可重復讀):對同一字段的多次讀取結果都是一致的,除非數據本身是被本身事務所修改的,可以阻止臟讀和不可重復讀,但幻讀仍有可能發生
- SETIALIZABLE(可串行化):最高的隔離級別,完全服從ACID原則,所有事務單個依次執行,這樣事務之間就完全不可能產生干擾,也就是說,該級別可以防止臟讀,不可重復讀以及幻讀。
MVCC原理
事務版本號
事務每次開啟前,都會從數據庫獲取一個自增長的事務ID,可以從事務ID判斷事務的執行先后順序
隱式字段
對于InnoDB存儲引擎,每一行記錄都有兩個隱藏列trx_id、roll_ptr,如果表中沒有主鍵和非NULL唯一鍵時,則還會有第三個隱藏的主鍵列row_id。
MySQL行記錄中除了記錄業務數據外,還有隱藏的 trx_id 和 roll_ptr
- trx_id:表示最近修改的事務的id,每次一個事務對某條聚集索引記錄進行改動時,都會把該事務的事務id賦值給trx_id隱藏列,新增一個事務時,trx_id就會遞增,因此trx_id能夠表示事務開始的先后順序
- roll_ptr:指向上一個版本的地址,每次對某條聚集索引記錄進行改動時,都會把舊版本寫入undo log中,然后這個隱藏列就相當于一個指針,可以通過它來找到該記錄修改前的信息。
版本鏈
MySQL的每行記錄邏輯上其實是一個鏈表
演示說明:
update user set name = '肉蟹寶1' where id = 1這個鏈表存在于undo log中,和最新版本的數據不在一起
每次更新后,都會將舊值放在一條undo log中,就算是該記錄的一個舊版本,隨著更新次數的增多,所有的版本都會被roll_ptr屬性連接成一個鏈表,我們把這個鏈表稱為版本鏈,版本鏈的頭節點就是當前記錄最新的值
另外,每個版本中還包含生成該版本時對應的事務id(trx_id)
ReadView
說完了undo log 和版本鏈,再來說說ReadView,前面我們說過MVCC只在read_commited和repeatable_read兩個隔離級別下工作,而read_commited和repeatable_read的區別不同就在于它們生成的ReadView的策略不同
對于使用read_commited和repeatable_read隔離級別的事務來說,都必須保證讀到已經提交了的事務修改過的記錄,也就是說假如另一個事務已經修改了記錄但未提交,是不能直接讀取最新版本的記錄的
核心問題就是:需要判斷一下,版本鏈中哪個版本是當前事務可見的
為此InnoDB提出了一個ReadView的概念,這個ReadView中有一個id列表,trx_ids來存儲系統中當前活躍著的讀寫事務,也就是begin了但是還未commit或rollback的事務
流程說明:
演示說明:
提交了trx_id是2的記錄后,接著新建trx_id為3的事務,修改name的值,但是事務還沒有提交
update user set name = '肉蟹寶 2 ' where id = 1則此時的版本鏈是:
顯然,此時的trx_ids為[3]
如果另一個事務查詢id為1的記錄,因為trx_ids當前只有trx_id為3的事務,而trx_ids的意義是記錄未完成的事務。在這里,事務未完成,所以該條記錄不可見,繼續查詢下一條,結果返回肉蟹寶1
此時我把trx_id為3的事務提交了,并且新建了一個trx_id為4的事務,修改name,且不提交事務
update user set name = '肉蟹寶 3 ' where id = 1這時的版本鏈就是:
read-committed —— 每次查詢數據前都生成一個 ReadView
trx_ids 將更新為[ 4 ],版本鏈通過 trx_id 對比查找到的結果就是肉蟹寶2。
repeatable-read —— 在第一次查詢數據時生成一個 ReadView,之后的讀都復用之前的。
不會有重建的 ReadView , trx_ids 還是 [ 3 ],MySQL 認為事務3未完成,所以 select 的結果是肉蟹寶1。第2次 select 結果和第1次一樣,所以叫可重復讀。
小結
從上邊的描述中我們可以看出來,所謂的MVCC--多版本并發控制指的就是在使用read-commited、repeatable-read這兩種隔離級別的事務在執行select操作時,訪問記錄的版本鏈的過程。這樣子就可以使不同的事務的讀-寫、寫-讀作并發執行,從而提升系統性能。
read-commited、repeatable-read這兩個隔離級別的很大一個不同就是:生成ReadView的時機不同
read-commited 在每一次進行select操作時都會生成一個ReadView,而repeatable-read只在第一次進行普通select操作時生成一個ReadView,之后的查詢操作都重復使用這個ReadView
參考與感謝:
深入理解MYSQL的MVCC機制
javaguide
總結
以上是生活随笔為你收集整理的什么是MVCC,一文搞懂MySQL的MVCC机制的全部內容,希望文章能夠幫你解決所遇到的問題。
- 上一篇: 阻塞和非阻塞IO,异步和同步IO
- 下一篇: mysql 事务原理 mvcc_MySq