CMU Advanced DB System - MVCC

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

 

Concurrency Control Protocol

併發控制方法,主要以下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 Storage

多版本,是用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來記錄引用次數

 

 

 

 

Garbage Collection

GC分爲兩個粒度,Tuple或是Transaction

 

 

Naive的想法,就是用一個後臺的線程,不斷的檢查每個tuple,而且把過時的Vacuum

方法的問題是,若是數據量很大,很難完成

 

 

 

Cooperative cleaning,在查詢的時候,要遍歷version chain,這個時候順便把舊的刪掉

可是問題是,若是tuple一直沒有被查詢,就不會被過時,因此仍是要輔助用Vacumming的方法

 

按照Transaction的維度,要求保存下r/w set,這樣能夠把不用的Transaction所建立的版本都過時掉

 

 

 

 

Index Management

主鍵,若是N2O,須要每次更新

二級索引,通常須要用logical pointers來下降update頻率

 

 

 

 給出各個現有的數據庫引擎,因此使用的MVCC的設計和實現,

 

 

 

MV-OCC

下面看兩個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,

 

 

相關文章
相關標籤/搜索