MVCC并发控制的实现原理

事务的隔离级别

传统隔离级别

其实是用于2PL而定义的。RU,会产生读脏、不可重复读、幻读。RC则不会读脏,RR不会读脏、可重复读。
可串行化则全部解决。读脏是指读到其他事务未提交的数据,其实是因为RU在实现时读行不加锁,修改才加锁,如果对同个行进行访问,那么其他事务就算加了锁,依旧能够读该行,导致出现读脏;不可重复读就更容易解释了,因为修改后第二次读取发现数据不一致,幻读则是由于行的插入和删除导致事务执行相同的select读取到的结果集不一样;然后是读已提交,它多加了读锁的实现,两阶段锁第一阶段只加锁,第二阶段释放锁,但是读已提交能够在收缩阶段加读锁;那么就意味着待其他事务提交后,再次读同个行会出现不一致的数据,这就是不可重复读。
可重复读,则在收缩阶段只能释放锁,保证了不会出现上述情况,但是依旧不能解决插入操作导致的幻读,需要多加一个index lock的实现才能解决,gap lock,也就是实现可串行化
在这里插入图片描述

快照隔离级别

快照隔离级别是多版本并发控制中引入的一种隔离级别。实际上MVCC只能实现读已提交,可重复读,而可串行化的实现需要引入乐观并发控制OCC或者2PL(悲观)来实现。
MVCC实现:
MVCC在不同的存储引擎中实现也不同,传统TP数据库如pgsql,mysql的out-of-memory实现中,采用读视图read view加上物理上存储数据的不同版本(pgsql append在原表中,mysql append在undo log中),事务的时间戳采用逻辑时间戳,时间戳大代表创建的时间晚;
读视图存在于每一个事务中,新建时事务创建一个事务,该事务会创建当前数据库事务快照集,通过这个快照集我们能确定哪些事务在该事务创建时是仍活跃的(未提交,因此我们在读取时不能读取到该事务创建的新版本数据);

快照读

通过read view来遍历数据的所有版本,找到能读取的版本,能解决读脏,不可重复读和幻读问题,同时读事务和写事务并不冲突(无需上锁),能解决读写冲突,但是不能解决写写冲突:假设事务A先更新了数据x,进行了提交,事务B早于事务A创建,很明显读不到事务A的新创建的版本,而是基于旧版本更新了数据x,这样一来把事务A的更新给覆盖了,也就是lost update。对于这种读称为快照读

当前读

为了保证在更新时能读取到最新的数据版本,我们就需要保证该数据此时不被其他事务所更改,因此就需要引入2pl。这样一来对同一数据的更新就可以实现冲突可串行化,保证顺序对数据进行读取后更新。

MVCC中的写偏序(write skew)

在前面,我们解决lost update,也就是对同一数据更新时如何保证冲突可串行化。但是还有一种情况,在MVCC中更新的是不同的数据,但是却也出现了更新错误,也就是写偏序。
写偏序是指MVCC的读写循环依赖。如下例子,事务A读取了事务B接下来要更新的元组B,事务B也读取了事务A接下来要更新的元组A,出现了两个读写依赖,造成了事务的循环依赖:
写偏序

因为MVCC的实现中,读(select)不加锁,在进行读取时能够读取到旧版本,有一种业务场景需要我们先判断某个条件再去更新其他数据。事务A中,我们发现数据tupleB符合条件后对其他数据tupleA进行加锁然后更新,但是在更新时却有其他事务B来更新这个条件(tupleB),那么就会出现逻辑错误。因此,我们引入了select的当前读机制。
在mysql的当前读机制中,select语句也可以加共享锁或排他锁,只需在语句最后加for update或for share即可。这样一来就可以保证写偏序的解决,这也就是可串行化的快照隔离级别。

实际上MVCC的实现非常灵活,没有标准。
sql-server的快照隔离

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值