最终一致性其实比MVCC简单

2020-03-16 18:28:19 浏览数 (1)

关于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中是一个复杂的主题,可以列举一大堆。

0 人点赞