任何一个数据库最主要功能之一是可扩展。若是不删除彼此,则尽量较少锁竞争从而达到这个目的。因为read、write、update、delete是数据库中最主要且频繁进行的操做,因此并发执行这些操做时不被阻塞则显得很是重要。为了达到这种目的,大部分数据库使用多版本并发控制(Multi-Version Concurrency Control)这种并发模型。这种模型可以将竞争减小到最低限度。web
MVCC是什么算法
Multi Version Concurrency Control ( MVCC)是这样的一种算法:经过对同一个对象维护多个版本,提供一种很好的并发控制技术,这种技术可以使READ和WRITE操做不发生冲突。这里的WRITE指的是UPDATE和DELETE,不包含Insert是由于新插入的记录能够经过各自的隔离级别进行保护。每一个WRITE操做使对象产生一个新版本,每一个并发读操做依赖于隔离级别读取对象不一样的版本。因为READ和WRITE操做同一个对象的不一样版本,因此这些操做不须要将对象彻底锁住,所以这些操做可以并发执行。固然当两个并发事务WRITE同一个记录时,这些锁竞争仍是会存在的。sql
当前大部分数据库系统都支持MVCC。这个算法的核心是对相同对象维护不一样版本,所以不一样数据库建立并维护多版本的方式不一样,其实现方式也不一样。相应地,数据库操做和数据存储也发生变化。数据库
实现MVCC最多见的方法:PostgreSQL使用的方法、InnoDB和Oracle的使用方法。下面咱们会详细讨论PG和InnoDB的实现方式。session
PostgreSQL中的MVCC并发
为了支持多版本,PG对每一个对象(PG术语:Tuple)增长了额外的字段:mvc
一、xmin:进行插入或更新操做事务的事务ID。UPDATE中,对tuple的新版本分配该事务ID。app
二、xmax:进行删除或更新操做事务的事务ID。UPDATE中,对当前存在的tuple分配该事务ID。新建立的tuple,该字段默认为null。ide
PostgreSQL将全部数据存储在HEAP中(每页默认8KB)。新记录的xmin为建立该记录的事务的事务ID;老版本(进行update或delete)其xmax为进行操做的事务的ID。会有一个链表将老版本和新版本链接起来。在回滚的过程当中,老版本记录能够被重用;依赖于隔离级别,READ语句读取一个老版本记录进行返回。post
例以下面两条记录:T1(值为1)、T2(值为2),经过下面3步对记录的建立进行演示:
从图中能够看出,数据库中初始时存在两个记录:1和2。
第二步,将2更新为3。此时建立一个新值,并存放到同一个存储区域的下一个位置。老版本2为其xmax分配该事务的ID,而且指向最新的版本记录。
同理,第三步,当T1被删除时,对记录进行虚拟删除(为其xmax分配当前事务ID),该操做不存在建立新记录版本。
下面,经过实例讲解每一个操做如何建立多版本,不用加锁如何实现事务的隔离级别。下面例子中使用默认隔离级别“READ COMMITTED”。
INSERT
每次insert一个记录,都会新建立一个tuple并将其存储到表文件的页中。
能够看到:
一、Session-A开启一个事务,其事务ID为495
二、Session-B开启一个事务,其事务ID为496
三、Session-A插入一个tuple,存储到HEAP
四、新tuple的xmin为495,而xmax为null
五、因为Session-A的事务没有提交,session-B看不到第3步插入的值
六、Session-A提交
七、均可以看到新插入的tuple
UPDATE
PostgreSQL的UPDATE不是“IN-PLACE”更新,不会将现有对象更新替换为新值,而是新建立一个新对象。所以UPDATE涉及如下几步:
一、将当前对象标记为deleted
二、插入对象的一个新版本
三、将对象的老版本指向新版本
所以,即便许多记录保持不变,HEAP也会占用空间,就像新插入另外一个记录同样。
如上所示:
一、Session-A开启一个事务,其事务ID为497
二、Session-B开启一个事务,其事务ID为498
三、Session-A更新一个现有记录
四、Session-A能够看到tuple的最新版本而Session-B看到另外一个老版本。Session-A看到新记录的xmin为497,xmax为null;Session-B看到老版本xmin是495,xmax为497即Session-A的事务ID。这两个tuple版本都存在HEAP中,若是空间容许甚至存在同一页中。
五、Session-A提交事务,老版本消失
六、如今全部会话均可以看到记录的同一个版本。
DELETE
DELETE操做和UPDATE相似,只是不会添加一个新版本。如UPDATE,只是将当前对象标记为已删除。
一、Session-A开启一个事务,事务ID为499
二、Session-B开启一个事务,事务ID为500
三、Session-A删除现有记录
四、Session-A看不到当前事务已删除的记录;Session-B看到老版本,其xmax为499,499的事务删除的该记录
五、Session-A提交事务,老版本记录消失
六、全部会话都看不到以前的老版本
能够看到,这些操做都不会直接删除现有记录,若是须要会添加一个附加版本。
咱们来看看SELECT在多版本中怎么执行:依赖于隔离级别,SELECT须要读取tuple的全部版本直到找到合适的tuple。假设有一个tuple T1,被更新为新版本T1’,而后再被更新为T1’’:
一、SELECT操做进入这个表的heap中,首先检查T1,若是T1的xmax事务已提交,查找该tuple的下一个版本
二、T1’也被提交,查找下一个版本
三、]最后找到T1’’看到xmax未提交或者为null,而后T1’’的xmin可见,最后读取T1’’这个tuple。
能够看到须要遍历该tuple的3个版本才能找到合适的可见版本,直到VACUUM进程回收了打上delete标签的记录。
InnoDB中的MVCC
为了支持多版本,InnoDB对行记录又额外维护了几个字段:
一、DB_TRX_ID:插入或更新航记录的事务的事务ID
二、DB_ROLL_PTR:即回滚指针,指向回滚段中的undo log record
与PostgreSQL相比,InnoDB也会建立行记录的多版本,可是存储老版本的方式不一样。
InnoDB将行记录的老版本存放到独立的表空间/存储空间(回滚段)。和PostgreSQL不一样,InnoDB仅将行记录最新版本存储到表的表空间中,而将老版本存放到回滚段。回滚段中的undo log做用:用来进行回滚操做;依赖于隔离级别,进行多版本读,读取老版本。
例如,两行记录:T1(值为1),T2(值为2),能够经过下面3步说明新记录的建立过程:
从上图能够看到,初始时,表中有两条记录1和2。
第二阶段,行记录T2值2被更新为3。此时记录建立一个新版本并替代老版本。老版本存储到回滚段(注意,回滚段中的数据仅包含更改值,即delta value),同时新版本行记录中的回滚指针指向回滚段中的老版本。和PostgreSQL不一样,InnoDB更新是“IN-PLACE”。
同理,第三步,删除T1而后将其标记为虚拟删除(仅在行记录指定的一个bit位上打上delete标签)并在回滚段中插入一个对应的新版本。一样回滚指针指向回滚段中undo log。
从表面上看,全部操做表象与PostgreSQL相同,只是多版本在内部存储方式不一样。
MVCC:PostgreSQL vs InnoDB
下面分析PostgreSQL和InnoDB的MVCC主要不一样在哪几方面:
一、老版本的大小
PostgreSQL仅更新tuple老版本的xmax,所以老版本的大小和相应插入的记录大小相同。这意味着,若是一个older tuple有3个版本,那么他们大小都相同(若是更新的值大小不一样,每次更新时实际大小就不一样)。
InnoDB的老版本存储到回滚段,且比对应的插入记录小,由于InnoDB仅将变化的值写到undo log。
二、INSERT操做
INSERT时,InnoDB会向回滚段写入额外的记录,而PostgreSQL仅在UPDATE中建立新版本。
三、回滚时恢复老版本
回滚时,PostgreSQL不用任何特定内容,需注意老版本的xmax等于update该记录的事务ID。所以在并发快照中该记录认为是alive的直到该事务ID的事务提交。
而InnoDB,一旦回滚,须要从新构造对象的老版本。
四、]回收老版本占用的空间
PG中,老版本占用的空间仅在没有并发快照使用时才能够被回收,此时被认为dead。而后VACCUM能够回收空间。VACCUM能够手动触发也能够依赖于配置在后台任务中触发。
InnoDB的undo log分为INSERT UNDO和UPDATE UNDO。事务提交后,就会当即释放INSERT UNDO。当没有其余并发快照使用时,才能够释放UPDATE UNDO。InnoDB没有显示VACUUM操做可是有相似的PURGE回收undo log。
五、延迟vacuum的影响
如前所示,PostgreSQL延迟vacuum存在很大影响。即便频繁执行delete,它将会引发表膨胀形成占用的存储空间暴增。这还会形成到达一个点后,须要执行一个高额代价的操做VACUUM FULL。
六、表膨胀时的顺序扫描
即便全部记录都是dead状态,PostgreSQL的顺序扫描也会扫描对象全部的老版本,直到执行vacuum将dead的记录删除。这是PG中常见且常常讨论的问题。主要PG将一个tuple的全部老版本都存储到同一个存储区域。
而InnoDB,除非须要,不然不须要读取undo log。若是全部undo记录都已失效,那么只须要读取全部对象的最新版本既可。
七、索引
PostgreSQL独立存储索引,并将索引链接到HEAP中的真实数据。所以即便没有更改索引,有时也须要更新索引。随后这个问题被HOT(Heap Only Tuple)解决,可是仍有限制,若是相同页空间不足,则退回到正常UPDATE操做。
InnoDB因为使用汇集索引,不会有这样的问题。
结论
PostgreSQL的MVCC有一些缺点,尤为是具备频繁UPDATE/DELETE负载时,会引发表膨胀。所以决定选择PG时,须要慎重配置VACUUM。
PG社区已经意识到这个问题,已经开始涉及基于undo的MVCC(暂命名为ZHEAP),咱们在将来版本能够看到这个特性。
原文
https://severalnines.com/blog/comparing-data-stores-postgresql-mvcc-vs-innodb