vlambda博客
学习文章列表

分布式事务、MVCC、事务隔离级别

我们都知道,OceanBase 是一个分布式数据库,数据是打散到多台服务器上的,当一个分布式事务要执行的时候,可能需要跨越多台 OB Server,如果在执行过程中遇到各种各样的异常情况,OceanBase 是如何确保 ACID 呢?这一章节我们就一起探讨下。

首先介绍下 ACID。事务指逻辑上的一组操作,这些操作要么全部成功,要么全部不成功。典型场景就是转账,A 账户的扣减,与 B 账户的增加,必须一起完成或者都不完成。事务有ACID,四个基本要素: 
A 指 原子性,需要保证操作都发生或都不发生。这在传统集中式数据库中比较容易实现,因为不存在跨机操作的风险,如果服务器故障了,所有的操作都不会完成,一般不会出现部分提交的现象。
但在分布式环境中就比较复杂,一个事务 10 条 sql 语句,涉及 8 个分区,极端情况下,这 8 个分区的主副本可能分布在 8 台服务器上,万一出现一些异常,有些完成了,有些没有完成,都会影响事务的原子性。OceanBase 是用两阶段提交协议保证原子性的。
C 是指 一致性,是指事务前后数据的完整性必须保持一致。OceanBase 通过保证主键唯一,全局快照等技术保证一致性。 
I 是指 隔离性,是指多个用户并发访问数据库时,数据库为每个用户开启的事务,不能被其他事务的操作所干扰。比如一个事务正在修改某个字段,这个字段是否允许其他事务读或者写呢?OceanBase 采用 MVCC 技术解决读写互斥的问题。
D 是指 持久性,一个事务一旦被成功提交,它对数据库中数据的改变就是永久性的,即使数据库发生故障也不应该对其有任何影响。这块我们之前重点讲了,OceanBase 使用 Paxos协议,通过在多副本之间同步 Redo-Log 和 Redo-Log 落盘来确保数据的持久性。
接下来我们重点讲下,A 和 I,即 OceanBase 如何在分布式场景下,保证事务的原子性和 隔离性。

分布式事务、MVCC、事务隔离级别

    首先看下如何保障 A,原子性。如果一个事务有 10 条 SQL 语句,涉及 8 个分区,极端情况下,这 8 个分区可能分布到 8 台服务器上,这时就是分布式事务了。

对于应用而言,是否需要应用知道分区具体分布到哪台服务器上,是不是需要应用采取一些技术手段,如两阶段提交,来保证事务的 ACID。答案是不需要的,对于 OceanBase 而言,OceanBase 内部是典型的分布式场景,很复杂。对外,对应用开发者来说,是一个简单的一阶段事务的场景,应用只要开启事务,执行 10 条 SQL 语句,提交事务或者回滚事务即可,跟使用一个集中式数据库差异不大。 

OceanBase 内部使用两阶段提交方式来保证事务的原子性,两阶段提交有两个重要角色,协调者和参与者。
两阶段的第一阶段是投票阶段, 协调者 向所有参与者发送 Prepare 请求与事务内容, 询问 是否可以准备事务,并等待参与者的响应。 参与者 执行 事务中的操作,向协调者返回事务操作的执行结果;
两阶段的第二阶段是执行阶段,如果所有参与者都返回 OK,协调者向所有参与者发送提交请求,参与者收到后,完成事务的提交,并返回给协调者。协调者收到所有参与者的消息后,完成事务,返回给应用。如果有任何一个参与者返回 no 或者超时未返回,则需要回滚。
那么在 OceanBase 内部,谁是参与者? 谁是协调者呢? 需要澄清的是,OB Proxy 不是协 调者,它是无状态的,也没法持久化数据,如果让它做协调者的话,一旦它宕机了,事务也无 法恢复了。 数据库中间件架构中,一般中间件是协调者,下面的数据库是参与者,参与者只知 道协调者而不知道其他参与者的存在,一旦中间件故障,很难恢复事务。
OceanBase 创新了二阶段提交的方案,所有的参与者和协调者都是 OB Server,参与者 和协调者都知道彼此的存在,参与者也知道其他参与者的存在,如图中所示,Zone2 的一个服 务器是协调者,当事务需要的数据在其他服务器上时,会由 Zone2 的服务器与 Zone1 和 Zone3 的相关服务器(这些服务器做为参与者)通信。
这个好处是,OB Server 是有高可用性保障的,事务执行期间,任何一台 OB 服务器故障, 即使是协调者所在的服务器故障了,其他服务器的从副本通过 Paxos 协议也可以很快的自动 选举新的主副本,新的主副本做为新的协调者,可以将事务恢复到宕机之前的状态,不会有状 态和数据的丢失,可以确保分布式事务的强一致性,避免事务部分提交的现象。
以上图为例,如果协调者所在服务器故障了,P2 从副本 所在的两个服务器会选出新的主副 本,新的主副本已经知晓该事务的存在(通过 Redo-Log 日志同步),并担任新的协调者的角 色来恢复业务。

OceanBase 分布式事务的能力在 TPC-C 测试中也得到了充分的验证:

  • TPC-C 测试要求 40%的订单需要跨仓库执行,这个时候,如果是一个分布式事务,需要以仓库为单位把数据打散,大概率很多事务是需要跨机器执行的。如此多的事务执行完成后,如何判定有没有事务存在部分提交的现象呢?审计员会扫描所有的数据(OceanBase 测试时候,大概扫了约 1 万亿条数据),算一个 SUM 值,跟其他几个值来对比,如果数值不等的话,肯定有一些分布式事务有问题,测试是不会通过的。

  • 为了更好的测试 OceanBase 的分布式事务,审计员也人工制造了一些故障,登陆一台阿里云 ECS 服务器后,直接把服务器 shutdown,这个服务器上肯定运行了大量的事务,接下来应用会报错和重连。审计员最后会再检查完整性和一致性,一旦有不一致的,测试也不会通过。传统数据库也可以完成上述测试,但往往是依靠底层的高端硬件来实现的,而 OceanBase 是完全依赖自身软件实现的。 


采用锁机制控制读写冲突时,对数据加锁后,往往其他事务将无法读,导致读写竞争,影响读的并发性,OceanBase 支持 MVCC 特性,可以有效解决该问题。如图上举例,张三的账户余额是 100 元。
A1 时间,T1 事务想要更改张三的账户余额为 50 元,此时会加锁,避免数据被其他事务改写。由于事务没有正式提交,系统以时间戳为版本号,记录两个版本的数据,新版本为 50,旧版本为 100.
A2 时间,T2 事务想查询张三的账户余额,由于新版本数据还未正式提交,存在回滚的风险,T2 事务可以读取小于等于当前版本号的数据,且是已提交的数据,读取金额为旧版本的数据,100.
A3 时间,T1 事务完成提交,张三的余额变为了 50.
A4 时间,T3 事务想要读取张三的余额,由于 T1 事务已经提交完成,此时 T3 事务可以读取新版本的数据,读取到的金额为 50。
总结下,OceanBase 使用 MVCC 技术解决了读写互斥的问题,提高了读的性能。同时,OceanBase 提供了全局唯一的时间戳服务,消除机器时钟差异带来的影响,方便全局管理版本号。 
  • 当修改数据时:首先需要获取行锁,然后再修改数据。事务未提交前,数据的新旧版本共存,通过不同的版本号区分。事务提交后,只有新的数据。

  • 当读取数据时:不需要加锁,也不需要关注数据是否已被加锁。先获取版本号,再去查找小于等于当前版本号的已提交的数据。

这一页我们讲下事务隔离级别,先讲几个概念。
  • 第一是脏读,也就是读取了未提交的数据,最后这数据又回滚到了,导致读了错的数据。

  • 二是不可重复读,在一个事务内,不同的时刻读同一批数据,结果不一样。

    也就第二次读之前,期间被别的事务更新了数据。

  • 三是幻读,指的是在同一个事务内,在操作过程中进行两次查询,第二次查询的结果包含了第一次查询中未出现的数据或者缺少了第一次查询中出现的数据,期间被别的事务插入或者删除了数据。 

OceanBase 支持多种事务隔离级别,基于全局一致的数据版本号管理,以不同的版本号策略实现不同的隔离级别。
OceanBase 支持两种隔离级别,一种是 Read-Committed,可以避免脏读,但存在不可重复读和幻读(默认);另外一种是 Serializable,可以避免脏读、不可重复读和幻读,这是最严格的隔离级别,但会影响性能。
OceanBase 不支持脏读,事务能够读取的数据肯定都是已提交的数据,不可能是未提交的数据,但有可能是已提交的旧数据。