您的位置:首页 > 数据库 > MySQL

MVCC 问答

2014-06-22 19:57 260 查看
转载自:http://qing.blog.sina.com.cn/1765738567/693f084733003vvn.html

Q:先要谢谢你的文章<海量存储>,很系统且讲清‘为什么’(而不是简单的‘是什么’),收获不少。

看到mvcc时 http://qing.blog.sina.com.cn/1765738567/693f08473300067j.html,有一些不理解,还望详解。

A: 

 

我先从原理开始,然后介绍mysql实现(MVCC实现非常自由。。我对一些实现也没有那么了解,有说错的地方就海涵了)

如果要提到MVCC,就应从他最原始的需求出发来看。

对同一个数据的访问来说,所谓的一致性和隔离性,其实就是针对以下四种情况的不同处理方式。

写写

写读

读写

读读

你可以用这四个冲突顺序,拼装出针对同一个数据的全部访问顺序。

 

那么如何能够限制这些请求的先后顺序呢? 一个最简单的方式就是加锁。

第一个人访问或写入数据的时候,其他人不能够写入数据。

但这样并发度明显是上不去的,于是自然要想到,有没有更快的方法呢?

那么在java里面,比加排他锁更快的方法就是加入读写锁咯。

它能够保证”读读“ 这个冲突不会相互阻塞。

然而,读写和写写这两个case,却还是要全部锁住的。

为了解决这个问题,才会有MVCC这个概念产生,对应java就是copyOnWrite . 

本质是为了让”读写“和”写读“冲突而出现的一种技术,每次针对一个数据的写入,都会把原有数据复制一份出来,然后写道新的地方去。这样,这样,如果访问顺序是写读写,对于读写锁来说等于加锁三次,而对于MVCC来说只需要针对写写加锁就可以了,甚至写写都可以不加锁。对吧?

怎么做到呢? 我们假定有个全局时间戳,每次事务都加1 。 那么对于”写读写“这个顺序,第一个写的时候申请事务id  0, 第二个读因为不修改数据所以事务id 不增加,第三个写时事务id加1. 那么数据的版本是0,1. 而读数据的时候的id是0.

这样,读取的数据是0这个版本,因为已经有1这个版本了,我们就可以推断0这个版本的数据一定是完整的,而不需要关心1这个版本是否完整的写入到了节点中,从而就不需要等待版本1的完整写入了。于是,写不阻塞读,读不阻塞写。

从而,写写,写读,读写,都可以相互不阻塞,提升了系统的并发度。

 

理解了copyOnWrite,再来看真实的场景 中还需要解决什么问题。

为了说明这个问题,我们就需要理解什么是事务, 所谓事务,并不是指更新或读取一条记录的。 事务是在事务开始后,你可能会更新的数据的集合。比如,update table set a = 1 where a > 10 . 那么A > 10的所有数据(绝对不止一条),都需要“同时”被更改。

如果有一些计算机的基本知识,你就应该知道,计算机从本质来说还是个图灵机实现,在目前是不可能“同时”更改所有数据的,但从需求来说又要有这样的需求,于是只能通过其他方式模拟,这个模拟的方法,就只能是加锁,将所有A>10的记录都加锁,然后再更新掉。

那么这样做的时候,就不大可能使用乐观方式来做写写更新了,以我个人的理解,乐观锁的前提是,在争用不明显的场景下,因为减少了上下文切换的开销,从而可以获得性能的提升,但如果假定我们要针对一组记录做多次频繁的更新时,就要权衡,到底是上下文切换的开销大呢,还是频繁rollback并且额外消耗大量cpu空转的开销大了。

于是你就知道了,为什么mysql 目前的实现里面,只做到了读不阻塞写,写不阻塞读。写和写是相互阻塞的,原因就是因为必须加锁保证数据完整性。

 

----------------

针对mysql的实现方式的介绍。

为了简化模型,我们只讨论插入的情况,实际上还有个删除要考虑,不过原理类似。

在每行记录上维持一个trx_id.

每一个事务开始的时候,trx_id都会增加,事务可以是显示的setAutoCommit(false),也可以是个普通的update insert 等。

然后,他还有个当时正在进行的事务的trx_id的列表,维持在系统信息里,所有正在运行的事务都会将自己的id记录在这个列表中,等到事务提交后则从这个列表中删除掉。

而每个事务又维持了”自己的“一个正在进行的事务trx_id的列表,这个列表是系统列表的一个snapshot.

 

在实际运行过程中,读的时候其实mysql是用事务trx_id列表中最小的trx_id去与数据列中的记录做比较的,只有比这个在当前正在运行的snapshot中最小的trx_id还要小的数据,才允许被读取出来。

如,

全局事务id 列表是: 100,101,120,150

当前事务的id列表snapshot是 : 120,150

那么,记录中那些小于120 ,或者自己事务id所更新的记录,并且符合未被删除这个条件的的(这个原理类似,我不在这里说是减少复杂度)。 才能够被当前事务读取。

然后,就来看看MVCC能做到的两个隔离级别,读已提交RC (其他事务提交后的数据立刻能够被当前事务看到), 和可重复读RR(其他事务提交后的事务在当前事务内不可见)。

了解了原理,不难推断如何做到RC和RR

RC , 在事务内,在开始事务时更新一次当前事务的id列表snapshot,并且在每次运行了一个更新的sql后,都更新当前事务的id列表snapshot . 

RR , 只在事务开始时更新当前事务的id列表snapshot 。

 

这就是mysql的mvcc实现方式。

我说的那个是以oracle的实现模式为模板的。。所以不大一样。 PG的实现也不一样。。不过核心思路就是我上面提到的那种。
内容来自用户分享和网络整理,不保证内容的准确性,如有侵权内容,可联系管理员处理 点击这里给我发消息
标签:  mvcc mysql