MVCC(Multiversion Concurrency Control,多版本并发控制)是数据库为了提高并发的一种机制,现已普遍运用于各大数据库系统中,如Oracle,MS SQL Server 2005+, Postgresql, Firebird, Maria等等,开源数据库MYSQL中流行的INNODB引擎也采用了类似的并发控制技术.本文就来说说INNODB的MVCC实现原理.
MVCC
MVCC(Multiversion Concurrency Control,多版本并发控制),即多版本并发控制技术,它使得大部分支持行锁的事务引擎,不再单纯的使用行锁来进行数据库的并发控制,取而代之的是,把数据库的行锁与行的多个版本结合起来,只需要很小的开销,就可以实现一致性非锁定读(在repeatable read隔离等级下),从而大大提高数据库系统的并发性能.
MVCC实现
MVCC是通过保存数据在某个时间点的快照来实现的. 不同存储引擎的MVCC实现是不同的,典型的有乐观并发控制和悲观并发控制,mysql的innodb则是使用的乐观锁机制,即在每次事务开始之前取出该行的版本号,再次取出时会比对该行数据的版本号是否是事务之前的版本号.
MVCC原理
对于mysql来说,MVCC由于其实现原理,只支持read committed和repeatable read隔离等级.
MVCC可以提供基于某个时间点的快照,使得对于事务看来,总是可以提供与事务开始时刻相一致的数据,而不管这个事务执行的时间有多长.所以在不同的事务看来,同一时刻看到的相同行的数据可能是不一样的,即一个行可能有多个版本.
其它innodb会为每行数据自动维护三个隐藏字段,这三个字段通过查询无法看到,字段如下:
字段1 | 字段2 | DB_TRX_ID | DB_ROLL_PTR | DB_ROW_ID |
---|---|---|---|---|
1 | yangming | |||
2 | zhouxingchi |
说明如下:
- DB_TRX_ID: 长6字节,表示插入或更新行的最后一个事务的事务标识符.另外,删除在内部被视为一个更新,其中有个特殊位可以用来标记为该行删除
- DB_ROLL_PTR: 长7字节,该字段用于指向当前记录项中的undo log中的记录,也就是该语句执行之前该行数据的地址
- DB_ROW_ID: 大小是6byte,该值随新行插入单调增加,当由innodb自动产生聚簇索引时,只有聚簇索引包括这个DB_ROW_ID的值,该值不会出现在其它索引中
更多的官方的说明请看这里
所以对于增删改查4个场景来说,mvcc流程有点不同,以当前事务的版本号为当前版本号且隔离级别为repeatable read:
select
只有同时满足了下面两个条件的行,才能被返回:
- 行的被修改版本号小于或者等于该事务号
- 行的被删除版本号要么没有被定义,要么大于事务的版本号:行的删除版本号如果没有被定义,说明该行没有被删除过;如果删除版本号大于当前事务的事务号,说明该行是被该事务后面启动的事务删除的,由于是repeatable read隔离等级,后开始的事务对数据的影响不应该被先开始的事务看见,所以该行应该被返回
insert
对新插入的行,行的更新版本被修改为该事务的事务号
delete(delete被视为更新操作)
对于删除,innodb直接把该行的被删除版本号设置为当前的事务号,相当于标记为删除,而不是实际删除(commit时删除)
update
在更新行的时候,innodb会把原来的行复制一份到回滚段中,然后新插入了一行记录,并保存其创建时间为当前事务的ID,同时保存当前事务ID到要行的删除位
从上面的上述策略可以看出,在读取数据的时候,innodb几乎不用获得任何锁, 每个查询都通过版本检查,只获得自己需要的数据版本,从而大大提高了系统的并发度.
但是为了实现多版本,innodb必须对每行增加相应的字段来存储版本信息,同时需要维护每一行的版本信息,而且在检索行的时候,需要进行版本的比较,因而降低了查询效率;innodb还必须定期清理不再需要的行版本,及时回收空间,这也增加了一些开销
一致性非锁定读(快照读)
在REPEATABLE READ事务隔离级别下,同一事务内的一致性读均会读取到该事务中第一个读创建的快照,其他事务在之后提交或未提交的更新对当前事务的读均不可见,除非提交了该事务并开启新事务发起新查询.
事务A查询某些数据,而事务B刚好在对它些数据进行修改,正常情况下事务B会对这些数据加x锁, 其它事务便不能获取任何锁了,事务A只能等待,直到事务B释放,但是此时事务A会从undo log中读取这些数据的’’前一次备份’’,大大提高的查询的并发,不至于大量的读请求饿死.
所以,mvcc跟undo log结合,给数据库的并发提高了很大一步.
undo跟redo相关信息,可参考这里