最终一致性其实比MVCC简单

关系数据库ACID或MVCC很简单是一个谎言,最终一致性其实比MVCC简单。Stay Curious在If Eventual Consistency Seems Hard, Wait Till You分析了MySQL和PostgreSQL,发现MVCC远不是那回事,还不如选择最终一致性。

关于NoSQL有这样一个误解:

NoSQL最大的谎言是其简单,其实不是,简单意味着开发人员和运营人员需要做很多难且复杂的事情,它们最终得重复实现数据库已经实现的事情。

当人们试图捍卫关系数据库时,没有人质疑这段误解,特别是在黑暗的2009-2010年,当时NoSQL还高喊No SQL,各种NoSQL数据库从地面下冒出来,大部分的他们都有些夸大其词。

但是,质疑NOSQL是虚假繁荣经济的同时,也可以同时质疑关系数据库的复杂性。

真正的事实是,没有简单的关系数据库,数据库有很多功能和行为甚至好像很简单,但是当可靠性 正确性和性能变得很重要时,还是需要深厚的知识。

最终一致是难的?
最终一致是难的,因为开发人员要负担额外责任,这可以从Dynamo Paper找到这一说法的来源:

Dynamo 目标是设计为一直在写的数据存储场景,这个需求强迫我们把冲突解决的复杂性推到读阶段,这样确保写操作就从来不会被拒绝,下一个设计选择是执行冲突解决过程,这可以被数据存储或应用程序完成,如果冲突解决是由数据存储解决,选择相当有限....

很多人误解为需要在每次读操作时实现数据库逻辑,其实只有极端情况才会,一些场景才需要每次读操作时检查和调和冲突的修改。

你能发现在类似系统中许多这样案例,如Riak文档中,有很晦涩的词语,是不是很像一个博士生在使用这样的系统?

针对这种挑战,很多NoSQL认为在分布式系统中需要权衡,这就带来了CAP定理,这类主题有点类似量子力学中的薛定谔的猫捉摸不定。

分布式系统是很难的! 这是不可否认。 但有更好的方法吗?

关系数据库有多简单呢?
所有分布式系统理论和最终一致性等等复杂性,让你不得不重新向往关系数据的简单,但是这是真的吗?每个人都知道如今服务器已经成为主流,你喜欢的关系数据库已经可以垂直伸缩扩展到支撑大型应用,那么这能继续保持简单吗?

让我们看看简单的含义。

在关系数据库中简单只是没有并发时的简单,如果增加了并发,分布式系统的复杂就进来安营扎寨了,因为分布式和并发从根本上解决问题的原理都是一样,其实,除非你是基于单核的只有一个写 一个读的数据库,也许不会需要并发,其实现实中每台服务器都有分布式系统在里面,到处都是分布式。

Preetam Jinka说:对不起,通过单写操作Mutex实现的序列化隔离好像不让人有什么印象,

并发操作在大部分系统中并没有好好实现,许多关系数据库对于并发取了一个漂亮的名称:多版本并发控制Multi-Version Concurrency Control (MVCC). 有人说它比最终一致简单。

它的工作有点类似这样:
1. 有标准的四个隔离层(ACID),通过权衡使用它们,能够防止不一致的行为。

2.在可重复读REPEATABLE READ中,很多人认为的理想隔离级别。在这里你可以得到一个读快照,让你能一直看到数据库不变的视图,即使背后底下已经发生改变,其实现原理是:通过保存行版本一直到不再需要。

3.其他隔离层,比如READ COMMITTED是坏的,因为它们并不能保护你免于底层实现的复杂性,而且它们不允许你有一个真正ACID实现,一个真正ACID实现四个级别属性:原子性Atomicity 一致性Consistency 隔离性Isolation和持久性Durability。

4.回到可重复读REPEATABLE READ,只有这个隔离级别被推荐,它真的简单,每件事都表现得你好像是一个用户,作为开发者你被建议使用数据库逻辑和其交互,你不必考虑有关并发的事务发生。

这真的比最终一致性的数据库简单吗?正确吗?

MVCC谎言大洞
很不幸,关系数据库和它们的MVCC已经远离了乌托邦,MVCC的现实是比我下面描述得复杂得多。

MVCC和ACID以非常复杂的方式交织在一起,ACID的第一个问题来自于它们自己,这四个属性几乎全部被误解了,如同误解CAP定理一样。一致性和隔离有什么区别?原定义看来每个都是另外一个的一半,都没有一种一致的方式去以彼此隔离地思考它们.

接下来是隔离级别,每个数据库实现不同,实现每个隔离级别有很多分歧的正确方法,这里面肯定存在问题,因为标准没有详细规定,大多数数据库又非常固执己见,看看PostreSQL 如何说:

PostreSQL 只提供三个隔离级别的理由是,只有一个明智的方法来实现标准隔离级别到多版本并发控制架构的映射。

MYSQL如此说:
InnoDB 使用不同的锁策略支持每个不同的事务隔离级别,使用REPEATABLE READ你能拥有一个高度一致性,这对于操作重要数据是很重要,或者如果你觉得重复结果和精确一致不如锁导致性能问题更重要的话,你可以使用READ COMMITTED 或 READ UNCOMMITTED等降低一致性要求,。SERIALIZABLE 比REPEATABLE READ更严格,只能使用在特殊场合,比如XA事务,会带来并发和死锁等麻烦问题。

好像 MySQL/InnoDB都断言四个级别都能实现,按照PostgreSQL文档的矛盾我们稍后会挖掘更多,这时我们首先注意InnoDB的MVCC行为,因为相对PostgreSQL它非常类似于Oracle,文档说:一些Oracle-like类似Oracle的隔离级别是相当于一致(无锁)读。

Microsoft SQL Server的锁和MVCC也很不同,四个不同行为有四个不同实现。

让我们看看详细情况:

InnoDB的MVCC
InnoDB的 MVCC 在一个高层上保存旧的记录行版本一直到它们不再需要创建过去的快照,它是锁住任何被修改的行记录。

我们可以逐个浏览四个级别的情况,最明显的是REPEATABLE READ,它的设计是让你Select查询一系列行记录,然后每次Select查询后能反复看到相同的行记录,前提是只要你保持事务状态。文档如此说:

在同样事务中所有一致性读操作会读到第一个读操作创建的快照。

听起来优雅和美丽。 丑陋变得特别的快:

在锁读情况下(为修改而读或共享模式的锁),UPDATE和DELETE语句的锁依赖于这个条语句是否使用唯一索引作为唯一搜索条件,或者范围类型的搜索条件,对于使用唯一索引作为唯一搜索条件下,InnoDB只是锁住发现的索引记录,不是在其之前使用gap锁。对于其他搜索情况,InnoDB锁住扫描到的索引范围,使用gap锁或next-key锁堵塞住其他会话插入gap覆盖的锁范围。

到底是发生了什么? 发生了抽象泄漏

这里有几个逻辑必须和实现细节的问题。MVCC模型试图以并发方式平衡一堆事情,这些事情是有逻辑矛盾的,并不能用这种方式处理,不管实现有多复杂,都不能通过边缘方式特殊处理异常的行为。


逻辑必须之一是,举例,你只能修改最新版本的一个行记录?如果你试图修改旧版本(这个版本包含在你的一致快照中),你就遇到麻烦了,最终只有一个真相,数据版本的冲突是不允许暴露给用户,它们是最终一致的,因为这个理由,你会遭遇各种问题。(这里省略4个复杂问题,见原文)

InnoDB试图追赶和超越SQL标准,标准允许在 REPEATABLE READ中幻读(phantom reads),但是InnoDB使用next-key锁和gap锁来回避了这个实现,将REPEATABLE READ可重复读变得更接近序列化SERIALIZABLE 级别,虽然没有像SERIALIZABLE使用令人厌恶的锁。PostgreSQL做了同样事情。

我只是触及了InnoDB如何处理事务,锁定,隔离级别,MVCC等表面上复杂性。 我不是在开玩笑。 有大量的官方手册需要认真研究和理解。 锁在InnoDB中是一个复杂的主题,可以列举一大堆。

PostgreSQL如何工作?

(省去作者一堆废话....)
首先,PostgreSQL缺省使用READ COMMITTED,这意味着如果你在一个事务中查询一些记录行,如果有其他事务正在修改它们,就需要等待,然后再查询一遍,你才会看到改变后的数据。PostgreSQL有许多Bug和特殊行为导致并不能像在MYSQL那样使用隔离级别。

一个SELECT查询(不是为修改或共享目的)只能看到查询开始之前的确认提交commit的数据,它从来不会看到未提交数据或在查询时被其他并发事务改变提交的数据。其实,一个SELECT查询看到是数据库在查询开始运行那一时刻的快照,无论如何,SELECT会看到自己事务中之前修改执行后的效果,即使这些修改没有提交。值得注意的是,即使是在同一个事务中两个成功SELECT命令会看到不同的数据,如果在第一个SELECT查询时有其他事务提交了数据的改变的话。

不仅仅是MySQL和PostgreSQL
其他系统的MVCC几乎都是依赖记录或行的多个版本,处理冲突都是在这些多版本之间以各种方式,有些复杂,有些简单,开发者已经日益被底层实现所影响。

开发人员如果要胜任这些系统,你就必须自己处理它们,在一次次与MYSQL咨询中,看到很多开发人员,包括我自己在内,编写应用程序触犯了MVCC实现和规则。 结果呢?

1.性能问题。
2.可用性问题。
3.死锁和其他错误。
4,bug。 应用程序使用数据库时可怕的,微妙的错误。

唯一能够避免这些问题办法是使用单写设计,而这样通常扩展性不好,许多改造了MyISAM数据库的开发人员不明白为什么MyISAM不能扩展。(Many a MyISAM has been reinvented by database developers who don’t understand why MyISAM doesn’t scale.)

回到最终一致性
与之前复杂性噩梦相比,我不能肯定最终一致性真的对于开发者来说很难处理,开发者总是需要注意实现的每个精确行为,我研究了一些最终一致数据库(虽然我承认我花了我的大部分职业生涯在深入研究InnoDB),看来难以想象Cassandra或Riak真的比InnoDB复杂。

....

[该贴被banq于2014-12-09 20:27修改过]

我理解这篇文章总体意思是:

现在进入多核时代,只要是运行在多核上多用户同时读写都回避不了分布式和并发这两个课题。我们不能因为我们熟悉关系数据库,就对之产生100%信任,其实MySQL 这些关系数据库在处理分布式 并发以及一致性上非常复杂,而且有问题在其中,会打折扣。

比如缺省设置READ COMMITTED隔离级别其实并不能带来真正高一致性,而REPEATABLE READ类似于串行序列化,拒绝并行计算,严重浪费多核资源。

所以,这些数据库其实要么是使用类似加锁技术实现事务,这种类似Java中同步锁等应用一样,这种锁的多线程并发很差;要么是使用一种类似最终一致性的READ COMMITTED,也并不能给你真正高一致性。

与其接受传统关系数据库教廷式的洗脑,不如打开这个盒子,实现Out-of-box,NoSQL是这种尝试,DDD和CQRS也是这种尝试,将分布式和并发主动权掌握在开发人员自己手里,而不是打破脑袋搜集各种关系数据库资料,拜山各种关系数据库牛人,这种行为才是真正封建迷信呢。

什么是真正封建迷信?就是自己无法或不愿主动研究自然的秘密,期待牛人大牛为自己指点迷路,迷信于一些牛人的神奇“智力”,缺乏对自己的自信。


[该贴被banq于2014-12-10 08:46修改过]