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的实现也不一样。。不过核心思路就是我上面提到的那种。
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的实现也不一样。。不过核心思路就是我上面提到的那种。
相关文章推荐
- “撒币”问答AI外挂能帮你赢取百万么?我们用实验说话
- 直播升级之战!金山云喊麦邀你参加直播问答,舅服你
- 直播问答同质化背后的弊病
- 怎么利用知识图谱构建智能问答系统?
- 年末收藏福利 | 百度的中文问答数据集WebQA
- 轻松理解MYSQL MVCC 实现机制
- 问答:怎样规划CSS 中 的命名方式 怎样看待 CSS 中 BEM 的命名方式?
- MVCC (Multiversion Concurrency Control) 多并发版本控制
- 最新保险专家问答集锦,这是我见过最精彩的回答!
- Python 爬取汽车领域问答语料(自用)
- LILO与分区问答(2)
- Java应用问答
- 裸设备和Oracle问答20例
- 协定问答: 定义服务间的会话 (.NET Designers)
- 搜索引擎常用搜索方法问答
- 通联无限常见问答
- WAP开发问答
- 雅虎公司C#笔试题,包括问答题和选择题两部分。试试看,你能回答出多少题,可以通过雅虎的考试吗?