3

别再用BASE做借口,来看真正的事务一致性

 1 year ago
source link: https://blog.51cto.com/JavaEdge/6979400
Go to the source link to view the article. You can view the picture content, updated content and better typesetting reading experience. If the link is broken, please click the button below to view the snapshot at that time.
neoserver,ios ssh client

别再用BASE做借口,来看真正的事务一致性

精选 原创

分布式数据库“强一致性”包括数据一致性和事务一致性两个方面。今天谈事务一致性。

不少人觉得ACID落伍,BASE为理论基础的NoSQL才是潮流。BASE是很宽泛定义,承诺有限。我认为,BASE的意义只在于放弃了ACID的一些特性,从而更简单地实现了高性能和可用性,达到一个新的平衡。但是,架构设计上的平衡往往都是阶段性的,随新技术突破,原来平衡点也自然改变。不说分布式数据库,就连NoSQL也开始增加对事务的支持。ACID已是后浪。

1 事务的ACID

数据库中,“事务”由多个操作构成的序列。1970年詹姆斯 · 格雷(Jim Gray)提出事务的ACID,将广义事务一致性具化到原子性、一致性、隔离性和持久性。先看他在 Transaction Processing Concepts and Techniques 给的定义:

Atomicity: Either all the changes from the transaction occur (writes, and messages sent), or none occur.

Consistency: The transaction preserves the integrity of stored information.

Isolation: Concurrently executing transactions see the stored information as if they were running serially (one after another).

Durability: Once a transaction commits, the changes it made (writes and messages sent) survive any system failures.

原子性:事务中的所有变更要么全部发生,要么一个也不发生。

一致性:事务要保持数据的完整性。

隔离性:多事务并行执行所得到的结果,与串行执行(一个接一个)完全相同。

持久性:一旦事务提交,它对数据的改变将被永久保留,不应受到任何系统故障的影响。

ACID对数据库的重要程度不同:

别再用BASE做借口,来看真正的事务一致性_数据库

1.1 一致性

存在感最低,可看作对 “事务”整体目标的阐述。没提出任何具体功能需求,所以数据库中难找到针对性设计。

1.2 持久性

它不仅是对数据库的基本要求。仔细琢磨持久性定义,发现核心思想就是要应对系统故障。

故障分为:

  1. 存储硬件无损、可恢复的故障。主要依托预写日志(Write Ahead Log, WAL)保证第一时间存储数据。WAL采用顺序写,保证数据库低延时响应。WAL是单体数据库成熟技术,NoSQL和分布式数据库都借鉴了过去
  2. 存储硬件损坏、不可恢复的故障。要用到日志复制技术,将本地日志及时同步到其他节点。实现方式大体有三种:第一种是单体数据库自带的同步或半同步的方式,其中半同步方式具有一定的容错能力,实践中被更多采用;第二种是将日志存储到共享存储系统上,后者会通过冗余存储保证日志的安全性,亚马逊的Aurora采用了这种方式,也被称为Share Storage;第三种是基于Paxos/Raft的共识算法同步日志数据,在分布式数据库中被广泛使用。无论采用哪种方式,目的都是保证在本地节点之外,至少有一份完整的日志可用于数据恢复。

1.3 原子性

数据库区别其他存储系统的重要标志。

单体数据库时代,原子性问题已妥善解决,但随向分布式架构转型,在引入不可靠网络因素后,原子性又成为挑战。

要在分布式架构支持原子性不容易,所以不少NoSQL产品都选择绕过这个问题,聚焦到那些对原子性不敏感的细分场景。如Google BigTable甚至不支持跨行事务。但这种妥协也造成NoSQL通用性不好。

本系列讨论的分布式数据库是在分布式架构上实现的关系型数据库,就必须支持事务,先要支持原子性。原子性,在实现机制上较为复杂,目标却简单,和分成多个级别的隔离性不同,原子性就只有支持和不支持。

1.4 隔离性

事务中最复杂的特性。隔离性分多个隔离级别,较低隔离级别就是在正确性妥协,将一些异常现象交给应用系统的开发人员,从而获得更好性能。

事务模型的发展过程就是在隔离性和性能间不断寻找更优平衡点。事务的核心就是隔离性。而不同产品在事务一致性上的差别,也完全体现在隔离性的实现等级。

2 ANSI SQL-92(SQL-92)

最早、最正式的对隔离级别的定义,定义的隔离级别和异常现象:

别再用BASE做借口,来看真正的事务一致性_串行化_02

虽然SQL-92得到广泛应用,不少数据库也都遵照这个标准来命名自己的隔离级别,但它对异常现象的分析还是过于简单了。1995年Jim Gray等发表论文“ A Critique of ANSI SQL Isolation Levels”(以下简称Critique),对于事务隔离性进行了更深入分析。Critique是数据库领域经典论文。

3 Critique:更严谨的隔离级别

3.1 幻读和写倾斜

丰富细化SQL-92内容,定义六种隔离级别和八种异常现象

最关注快照隔离(Snapshot Isolation, SI)级。SQL-92的可重复读(Repeatable Read, RR)与可串行化(Serializable)主要差别是幻读(Phantom)处理。似乎说解决幻读就是可串行化。但随Critique发表,快照隔离明确提出,这说法就不适用了,因为快照隔离能解决幻读,但无法处理写倾斜(Write Skew),也不符合可串行化要求。因为翻译原因,有时写倾斜也称写偏序。

因此,使用最广泛的隔离级别:已提交读、可重复读、快照隔离、可串行化。

幻读和写倾斜是通往最高隔离级别的两座大山。

3.2 Critique的幻读

事务T1特定查询条件获得一个结果集,事务T2插入新数据且符合T1刚执行的查询条件。T2提交成功后,T1再次执行同样查询,得到结果集增大。

幻读 V.S 不可重复读

自然语义很接近,都是在一个事务内用相同条件查询两次,但两次结果不一:

  • 不可重复读,第二次结果集相对第一次,有些记录被修改(Update)或删除(Delete)
  • 幻读是第二次结果集里出现第一次结果集没有的记录(Insert)。幻读是在第一次结果集的记录“间隙”中增加了新的记录。所以,MySQL将防幻读的锁名间隙锁(Gap Lock)。

3.3 写倾斜

跟幻读相比,写倾斜稍复杂。

箱子里有三个白球和三个黑球,两个事务(T1,T2)并发修改,不知道对方的存在。T1要让6个球都变成白色;T2则希望6个球都变成黑色。

别再用BASE做借口,来看真正的事务一致性_串行化_03
别再用BASE做借口,来看真正的事务一致性_数据库_04

你看,最终的执行结果是,盒子里仍然有三个黑球和三个白球。如果你还没有发现问题,可以看看下面我画的串行执行的效果图,比较一下有什么不同。

别再用BASE做借口,来看真正的事务一致性_数据库_05

如果先执行T1再执行T2,6个球都会变成黑色;调换T1与T2的顺序,则6个球都是白色。

根据可串行化的定义,“多事务并行执行所得到的结果,与串行执行(一个接一个)完全相同”。比照两张图,很容易发现事务并行执行没有达到串行的同等效果,所以这是一种异常现象。也可以说,写倾斜是一种更不易察觉的更新丢失。

为搞清Critique中六种隔离级别的强弱关系以及相互间的差距,我截取了原论文的一张配图。

别再用BASE做借口,来看真正的事务一致性_串行化_06

你可以看到“快照隔离”与“可重复读”在强度上并列,“已提交读”则弱于这两者。事实上,今天大多数数据库支持的隔离级别就在这三者之中。

快照隔离 & MVCC

你可能会问,既然“快照隔离”这么重要,为什么会被SQL-92漏掉呢?

这是由于SQL-92主要考虑了基于锁(Lock-base)的并发控制,而快照隔离的实现基础则是多版本并发控制(MVCC),很可能是由于当时MVCC的应用还不普遍。当然,后来,MVCC成为一项非常重要的技术,一些经典教材会将MVCC作为一种独立的选择,与乐观并发控制和悲观并发控制并列。其实,在现代数据库中MVCC已经成为一种底层技术,用于更高效地实现乐观或悲观并发控制。有了MVCC这个基础,快照隔离就成为一个普遍存在的隔离级别了。有关MVCC的话题,我会在第11讲中继续展开。

4 隔离性的产品

为什么不支持最高级别可串行化?很长一段时间内,学术界都没有找到足够高效的并发控制技术。可能你熟悉的很多数据库声称提供了“可串行化”级别,但这往往只是一种形象工程,因为它们都采用的是两阶段封锁协议,导致性能无法满足生产环境的要求。不过,有些消息让人振奋,虽然不是普适的方案,但少数产品的尝试已经取得进展。

这种尝试来自:

第一个方向是,用真正的串行化实现“可串行化”隔离。我们往往认为多线程并发在性能上更优,但Redis和VoltDB确实通过串行化执行事务的方式获得了不错的性能。考虑到VoltDB作为一款分布式数据库的复杂度,其成功就更为难得了。我想,其中部分原因可能在于内存的大量使用,加速了数据计算的过程。另外,VoltDB以存储过程为逻辑载体的方式,也使得事务有了更多的优化机会。

如果说第一个方向有点剑走偏锋,那第二个方向就是硬桥硬马了。没错,还是在并发技术上继续做文章。PostgreSQL在2008年提出了Serializable Snapshot Isolation (SSI),这实际就是可串行化。而后,兼容PostgreSQL生态的CockroachDB,也同样选择支持SSI,而且是唯一支持的隔离级别。

这两个方向的尝试都很有趣,我还会在后续的课程中与你深入探讨。

5 分布式数据库的强一致性

数据一致性和事务一致性,它们共同构成了分布式数据库的强一致性这个概念。

别再用BASE做借口,来看真正的事务一致性_数据库_07

图片原始出处是论文“Highly Available Transactions: Virtues and Limitations”, Jepsen网站的简化版

这幅图展现了一个树状结构,左右两个分支上体现事务一致性和数据一致性的各个级别及强弱关系,根节点则体现了分布式数据库的一致性来自两者的融合。图中使用了不同颜色,简单来说,这是区别不同的一致性级别所需付出的性能代价。

对分布式数据而言,最高级别的一致性是严格串行化(Strict Serializable),Spanner实现的“外部数据一致性”可以被视为与 “Strict Serializable” 等效。但由于两条路径上各自实现难度及性能上的损耗,少有分布式数据库在顶端汇合。即使强大的Spanner也提供了有界旧一致性(Bounded Stale),用于平衡性能和一致性之间的冲突。

分布式数据库产品的“一致性”实现情况。

别再用BASE做借口,来看真正的事务一致性_数据库_08

OceanBase在2.2版本还增加了对“可串行化”的支持,但这是一个被Oracle重新定义的“可串行化”,在这个级别OceanBase和Oracle一样都会出现写倾斜。所以,这不是我们标准的隔离级别,没体现在表格。

  1. 数据一致性关注的是单对象、单操作在多副本上的一致性,事务一致性则是关注多对象、多操作在单副本上的一致性,分布式数据库的一致性是数据一致性与事务一致性的融合。
  2. 广义上的事务一致性被细化为ACID四个方面,其中原子性的实现依赖于隔离性的并发控制技术和持久性的日志技术。
  3. 隔离性是事务的核心。降低隔离级别,其实就是在正确性上做妥协,将一些异常现象交给应用系统的开发人员去解决,从而获得更好的性能。所以,除“可串行化”以外的隔离级别,都有无法处理的异常现象。
  4. 研究人员将隔离级别分为六级,你需要重点关注其中四个,分别是已提交读、可重复读、快照隔离、可串行化。前三者是单体数据库或分布式数据库中普遍提供的,可串行化仅在少数产品中提供。

严格意义上,分布式数据库的“强一致性”意味着严格串行化(Strict Serializable),目前我们熟知的产品中只有Spanner达到了这个标准,其同时也带来了性能上的巨大开销。如果我们稍稍放松标准,那么“数据一致性”达到因果一致性且“事务一致性”达到已提交读,即可认为是相对的“强一致性”。还有一点非常重要,分布式数据一致性并不是越高越好,还要与可用性、性能指标结合,否则就成了形象工程。

  • 打赏
  • 收藏
  • 评论
  • 分享
  • 举报

About Joyk


Aggregate valuable and interesting links.
Joyk means Joy of geeK