MVCC 之传统数据库和NOSQL

Posted NOSQL追随者

tags:

篇首语:本文由小常识网(cha138.com)小编为大家整理,主要介绍了MVCC 之传统数据库和NOSQL相关的知识,希望对你有一定的参考价值。

当前浏览器不支持播放音乐或语音,请在微信或其他浏览器中播放 MVCC 之传统数据库和NOSQL 以此文迎杭州初雪,如此大雪让我忆起了不少少年的记忆。

这篇文章首先将介绍MVCC(多版本并发控制,Multi-Version Concurrency Control)是什么,然后说明为什么MVCC对传统数据如此重要,而NOSQL数据库对MVCC则不太感冒,以及什么时候NOSQL数据库也需要MVCC

我们先给一个例子,你和你老婆通过数据库来交流早上买菜应该买什么,你老婆每天向数据库插入一条记录,说明应该买什么菜,你每天读取数据库,然后去执行,这是一个很和谐的画面。比如5号早晨8点钟,你老婆向数据库插入一条记录,让你买2个萝卜、3个白菜、1斤排骨,1秒之后这条记录插入完毕,然后你9点钟起床读到了这条记录就可以去买菜了。

日期

萝卜

白菜

排骨

2015/12/5

2

3

1

2015/12/6

2

1

3

有了例子,我们就可以正式开始了。

首先,我们解释下MVCC的本质以及为什么会出现。MVCC为解决并发读写访问的性能而生。注意两个关键词,一个是并发读写一个是性能,

  1. 并发读写:在这里可以简单理解为就是同时读写一行数据。还是说上面的例子,你老婆在2015-12-06 08:01:01.300的时候插入了一条记录,而你某天发神经早起了,你在2015-12-06 08:01:01.320的时候就开始读取,你们俩几乎同时对这行数据做了读写操作,这就叫做并发。并发会带来什么问题呢?你老婆要求买萝卜、白菜和排骨,可是数据库刚刚将萝卜写成功,还在写白菜和排骨,你来读了,那么你只看到了要买萝卜,你觉得今天可以少点体力活了?NO,待会你就完了。为了保证不犯错,数据库就强制要求,在你老婆插入记录的时候,你是不能读的,只能等啊等,直到你老婆将整体记录插入完毕,你才能得到真正的买菜任务,然后你可以愉快的去扛包了。上面同时解释了一致性的问题,如果你领导的命令是不完整的,数据库领域就成为数据不一致,是非常严重的问题。数据库层面实现读必须等写完成,一般通过锁来实现。

  2. 性能影响:技术同学都知道锁是影响并发的,对外表现就是影响性能,如果你一天买一次菜,只有你一个人买菜那么也没啥问题,但是如果你做了个平台,为全中国的家庭提供买菜计划,有些老婆又比较易变,刚开始要买3斤排骨,10分钟就改了注意要买1斤,此时如果读总是等待写,那么性能就会很难接受,技术男就会很快离开你的平台。

MVCC要解决的核心问题就是避免读等待写,假设你老婆在2015-12-06 08:01:01 发布了买菜计划,然后2015-12-06 08:01:10 又修改了买菜计划,那么数据库数据视图如下表。

日期

版本

萝卜

白菜

排骨

2015/12/5

1

2

3

1

2015/12/6 08:01:01

1

2

1

3

2015/12/6 08:01:10

2

2

1

1

在你老婆修改买菜计划的时候,2015-12-0608:01:10,你正好准备读取命令去买菜,此时,你不会等待你老婆修改买菜计划完毕,你会直接读取你老婆第一个版本的买菜计划,然后你就去执行了。所谓MVCC里面的多版本上面已经描述清楚了,好处是明显的,你不必等待你老婆修改完毕,直接读取了老版本的数据,节约了时间,提高了性能。有得必有失,性能好了,代价也是明显的,没有执行最新的命令,好好思考如何解决吧。

上面的说明已经解释了MVCC的优缺点,是否使用MVCC完全要看业务的考虑。比如你淘宝买东西,第一次刷页面,库存100,等下单的时候,告诉你已经卖完了,这是因为你看到的商品同时有很多人跟你抢,你还在想买不买,人家钱都付掉了,而你也不会到法院告淘宝为什么不让你买到。换个业务,如果是你的支付宝,你刷100遍里面的钱也不会变,否则你肯定要发疯了。当然,要是你老婆在刷你的账号,另当别论。

介绍了MVCC之后,再来简单说说为什么传统数据库对MVCC如此重要。先看事实,Oraclemysql InnodbDB2PostgresSQL主流数据库均支持MVCC。再简单分析下,传统数据库一般采用B+结构存储数据,数据修改粒度是页面,如果有对数据做更新,就要先将旧数据页面读取上来,如果要读取的页面不在内存中,而在磁盘上,性能就极其的不好了,因为SSD出现之前的SATA磁盘每秒提供的读写不过200次,每次操作数十个毫秒。如果采用加锁的方式,读等待写,那么读就会受到很大的影响,而很多业务非常关心读,胜过关心写。这时候如果并非金融支付类应用,MVCC是确实能够大幅的提升读性能的,如果此时性能还是不行,就分库分表、或者在业务层和数据库层中间加各种缓存结构,比如Memcache/Redis等,当然又多了处理数据库和cache的一致性问题。总结来看,MVCC之所以对传统数据库很有用,就是因为传统数据库写太慢了,MVCC解决了读等写的问题。

那么为什么很多NOSQL数据库完全未提及MVCC,也没看到性能问题呢?因为绝大部分熟知的NOSQL数据库基于LSM结构(见[2], [3]以及下图)实现,不会对已有数据做任何修改,而只是覆盖和标记删除,没有修改就没有磁盘读,写的时候只需要考虑写日志,写性能大幅提升,对读的影响小了很多。还有一个大杀器,就是NOSQL一般只提供极其简单的事务语义,比如单行原子性,从而在很多场景下能做到读根本不必等写,性能上看,等于获得了传统数据库MVCC的优势。这个优势并非NOSQL技术多么牛逼,而是适当裁剪了功能。

是不是MVCCNOSQL毫无用处?显然不是。上面说到,NOSQL因为裁剪了事务语义,所以读可以不等写就继续进行,但是如果实现更丰富的功能,比如LocalIndex,此时内存提交需要多次完成(HbaseColumnFamily也是如此,不过这个feature用的人不多),读就必须等写否则不一致就会出现,就容易出现家庭矛盾。第二个是分布式事务,此时NOSQL的写性能就跟传统数据库一样慢了,因为写操作中,可能面临着读磁盘,面临跨机器的IO。同时,因为修改会发生在多个节点上,也就是说写出现了多次提交,读无法绕过写,这时候NOSQL就完全面临传统数据库读写互斥的问题,此时MVCC就很有必要。

我们再以全局二级索引(GlobalSecondaryIndex)来说明MVCC如何在NOSQL中发挥作用。全局二级索引在传统数据库上早已经支持,因为传统数据库多是单机版,全局二级索引就是普通二级索引,不必处理多次IO的问题,实现相对容易。到了NOSQL,最大的特性就是扩展性,单表数百T数据是常态,跨多台机器也是必然,此时要做全局二级索引就没那么容易,基本上两种思路:

一种思路是异步,即主表数据写完立刻返回,然后后台线程再去构建索引,业务如果写完主表立刻查询索引,是有可能查不到的,这种方法简单,有其特定的业务场景,比如搜索。我个人认为并不好用,因为索引什么时候能建立好是完全没有保证的,出了问题几个小时也未必能建立好,大部分业务都很难处理好这种一致性问题。某些HBase的非官方实现、以及AWSDynamoDB是这么实现的。

另一种思路是实现分布式事务,分布式事务是全局二级索引的泛化,实现了之后自然就支持了全局二级索引,但是分布式事务难度不是一般的大,即使传统数据库发展了几十年,分布式事务也没有既保证一致性好又保证性能好的思路,多是根据业务需求做相应的裁剪和优化。在NOSQL上,目前比较流行的分布式事务实现是GooglePercolator(小米做了个开源的)和Yahoo!OMID(本身开源)。前者基于锁,理解较容易后者期望实现无锁的分布式事务,依靠觉得基于锁影响性能,于是提出了一个不要锁的,但是有些关于内存和事务执行时间的约束。整体看,我觉得前者靠谱点,因为分布式事务已经很复杂了,再加上无锁,工程复杂度可想而知。

第三种思路,可以为全局二级索引实现特定的分布式事务,当然也是面向主要使用场景对二级索引做裁剪,比如不支持唯一性检查,最终是希望为二级索引设计的分布式事务不会有任何写冲突,这就能让分布式事务的实现复杂度降低很多,基本上就等于Percolator的无锁版,但是不是靠OMID的工程实现实现无锁,而是直接砍掉了冲突,也就砍掉了锁需求。这个思路的主要考量是当前数据量越来越大,业务需要各种索引快速查询,但是真正需要完整分布式事务的寥寥无几,即使有,比如金融、库存,也很少敢直接使用NOSQL的,所以NOSQL暂时可以不做完善的分布式事务支持。这种思路我目前尚未看到谁提出,也许有人做了只是失败了没曝光,我会试试看。



参考文章:

[1]. https://en.wikipedia.org/wiki/Multiversion_concurrency_control基本MVCC概念

[2]. http://itindex.net/detail/52845-lsm-%E7%BB%84%E7%BB%87%E7%BB%93%E6%9E%84-bangerlee 简单LSM介绍

[3]. http://www.benstopford.com/2015/02/14/log-structured-merge-trees/详细的LSM介绍

[4]. http://www.open-open.com/doc/view/4bf8f47dcaa94ca4b3c3db1532a4f80d传统数据库MVCC

[5]. http://research.google.com/pubs/archive/36726.pdfGoogle Percolator

[6]. https://github.com/XiaoMi/themisGoogle Percolator的小米开源实现

[7]. https://github.com/yahoo/omid Yahoo的无锁分布式事务开源实现OMID


备注:

  1. 封面图片来自:http://www.marklogic.com/wp-content/uploads/2015/01/acid-transactions.jpg



以上是关于MVCC 之传统数据库和NOSQL的主要内容,如果未能解决你的问题,请参考以下文章

NoSql之CassandraMongoDBRedisHBase比较

Nosql数据库之mongodb基本操作(01)

MVCC事务总结

从应用角度谈谈NoSQL 数据库和关系型数据库的区别之处

NoSQL之Redis入门

MongoDB之NoSQL