https://zhuanlan.zhihu.com/p/40208895 Mysql的MVCC實現sql
https://severalnines.com/database-blog/comparing-data-stores-postgresql-mvcc-vs-innodb數據庫
第一種實現方式是將數據記錄的多個版本保存在數據庫中,當這些不一樣版本數據再也不須要時,垃圾收集器回收這些記錄。這個方式被PostgreSQL和Firebird/Interbase採用,SQL Server使用的相似機制,所不一樣的是舊版本數據不是保存在數據庫中,而保存在不一樣於主數據庫的另一個數據庫tempdb中併發
第二種實現方式只在數據庫保存最新版本的數據,可是會在使用undo時動態重構舊版本數據,這種方式被Oracle和MySQL/InnoDB使用。mvc
參考,An Empirical Evaluation of In-Memory Multi-Version Concurrency Controloop
Andy號稱是關於MVCC最好的paperpost
MVCC也是一個很老的技術,性能
主要目的,就是讓讀寫互不干擾,這樣讀寫之間不用加鎖,能夠簡單的增大併發lua
使用MVCC達到的隔離級別,是Snapshot Isolation,SI,若是要實現serializable,須要額外作一些事情 線程
雖然各個數據庫當前都採用MVCC,但具體設計和實現卻各不相同設計
MVCC設計和實現要考慮的點主要以下四點,
Concurrency Control Protocol
Version Storage
Garbage Collection
Index Management
併發控制方法,主要以下3種,
爲了支持MVCC,Tuple的格式以下,
先看MVTO,
對於MVTO,關鍵的meta是Read-Ts,用於表示最新一次讀取的id
讀寫的過程以下,
讀的時候,須要更新read-ts到10,那麼小於10的write不能更新
寫的時候,首先Txn-id,用於write lock,mvcc對於ww衝突仍然是要避免的,因此tx更新時,先把tuple的Txn-id設爲當前id,10,tx commit後,txn-id會置0
生成B2版本,B1的end-ts更新爲10,表示B1的生命週期從1到10
再看下,MV2PL,不一樣的meta是Read-cnt
read-cnt,表示多少tx在讀,做爲shared lock
txn-id和read-cnt,做爲exclusive lock
讀,首先txn-id爲0,表示沒有txn在寫,而後read-cnt須要加1
寫,首先txn-id和read-cnt都爲0,表示沒有讀寫,而後同時把兩個給設置成10,加1
這有個問題,ts會到上限,須要wrapRound
解決的方法,用flag標識frozen,新的txn id老是比frozen版本更新
Occ這裏沒有介紹,後面會詳細介紹
多版本,是用version chain來存儲,存儲的方式分爲3種
既然是用chain來存儲,就會有順序,
O2N,若是查詢新的很麻煩,須要遍歷,通常都是查詢Newest比較多
N2O,每次都須要改head
Append-only,多版本都放在Main Table裏面,比較簡單,版本之間經過point關聯
問題是,若是version多了,主表膨脹太快,很差維護
Time-Travel Storage
把old version放到一個臨時表,以tuple爲單位
這樣主表只有最新版本
在臨時表中,只會記錄Delta,改了一個attribute就記錄這個attribute的delta,tuple中沒有改的部分不用記錄
Non-inline的value,在多版本的時候如何處理,確定不能存多份,用Ref,而且用ref counters來記錄引用次數
GC分爲兩個粒度,Tuple或是Transaction
Naive的想法,就是用一個後臺的線程,不斷的檢查每個tuple,而且把過時的Vacuum
方法的問題是,若是數據量很大,很難完成
Cooperative cleaning,在查詢的時候,要遍歷version chain,這個時候順便把舊的刪掉
可是問題是,若是tuple一直沒有被查詢,就不會被過時,因此仍是要輔助用Vacumming的方法
按照Transaction的維度,要求保存下r/w set,這樣能夠把不用的Transaction所建立的版本都過時掉
主鍵,若是N2O,須要每次更新
二級索引,通常須要用logical pointers來下降update頻率
給出各個現有的數據庫引擎,因此使用的MVCC的設計和實現,
下面看兩個OCC的實現的例子,
首先是mssql的Hekaton,
Hekaton是MVOCC,Append-only,因此多版本都存儲在main table
OCC是樂觀鎖,衝突是經過validation來保證
因此讀的時候,不須要作ts的更新,找到相應的ts段讀取相應的版本
寫的時候,增長新的版本,並更新ts,這個時候ts是個更新中的狀態
直到commit,ts纔會更新成正常的commit ts
若是這個時候,發生寫寫衝突,以下圖,看看這個txn@25,就知道已經被更新,拋出異常
OCC比較關鍵的步驟是validating
若是要發現衝突,須要每一個Transaction記錄下Read,Write,Scan Set,還有Commit依賴
Hekaton的優勢是無鎖,除了獲取全局自增的txn id
問題是,Read/Scan set validation可能很是的貴,若是txn訪問了大量數據,因此這個適用於TP場景,若是是AP場景會有問題
AP若是要Scan數據,性能不高;Record級別的互斥比較粗,也許兩個Transaction寫的是相同record不一樣的column
Hyper是德國人作的學術性數據庫
採用的是MVOCC,Delta存儲的方式
存儲格式以下圖,
仍是論文裏面的圖更清晰一些,
帳號中,剛開始每一個人都是10,如今經過3個Transaction,從Sally轉1到henry,wendy,mike
Hyper在主表中存的是最新的數據,最新數據能夠in-place更新,這樣避免index的更新
經過一個單獨的列,VersionVector來指向delta,delta記錄的其實就是undo buffer的chain
例子中,T3,T5已經commit,T6還在進行中
對於Hyper,最關鍵的創新在validation階段,提出precision locking,來知足serializability
首先,只取validate,那些在當前txn開始後,committed的txns
由於若是以前就committed,你讀到的必定是最新的值,若是以後commit,那就是他commit的時候須要validate,跟我無關
根據txn中每一個sql的predicate的範圍,看看哪些committed的txns所更新的值,是否在範圍中
底下兩張圖是都不在範圍中
這張圖,發生了衝突,因此須要回滾
txn讀到的數據,被txn#1003修改了,不可重複讀
version vector有個問題是,Transaction完成後,delta會被回收掉,因此大部分version vector都是空的,因此要找出哪些非空會比較低效
因此synopses會標記出,什麼範圍內有非空的vector,這個例子中是在2,5之間會有
介紹一下Hana的MVcc設計,MV2PL,time-travel存儲
比較奇葩,Hana是N2O,可是main table裏面放的是oldest版本
用一個flag來標識是否有新版本,讀新版本至少要多一跳,經過獨立的hashtable
版本所對應的txn的meta也是經過pointer指向一個獨立的Txn meta data的空間
MVCC問題,
版本通常是用chain保存,須要去search chain來找到版本
須要額外的GC模塊來過時老的版本
id或ts的分配有全局瓶頸
這裏還介紹了一種CMU的CICADA,