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,