数据库 频道

分析数据库的事务隔离级别在数据库选型分析的时候很重要

今天写这篇文章花的时间有点长,分析如此复杂的问题,也难免会有些疏漏和错误。如果文中哪些地方写得不对,请多指正。

昨天一个信创项目要发对十几个数据库产品进行评估,需要对数据库产品设置少量几个评估项,项目不能太多,但是要能够反映出数据库的兼容性和通用数据库能力。我们以前的横向对比里面关于这方面的参数指标有二十几个,要想用两三个来代表确实有难度。最后想来想去,只选择了两个,一个是SQL标准遵循度,一个是事务隔离级别。国产数据库的SQL标准遵循度一般来说都会说是遵循SQL 1999/SQL 2003等,哪怕SQL引擎是完全使用MYSQL这种对SQL标准遵循度不是很好的数据库产品,都会号称支持,不过大多数都会加上一个“核心标准”,可能字比较小,很多人都不会关注,有些产品干脆就号称完全支持,现在几乎没有一款数据库能完全支持这些SQL标准,写完全支持的反而可以直接认为他们在撒谎了。

事务隔离级别可能回让人感到有些意外,评估事务隔离级别很重要吗?有可能大多数的企业都还没有使用标准事务隔离级别以外的其他级别。数据库系统是个十分复杂的系统,存储引擎、SQL引擎是其中的两个十分关键的组件,我们要去一点点分析其实现是十分困难的。不过通过事务隔离级别的分析,可以大致了解其强一致性与MVCC实现的一些细节。事务隔离级别在SQL1992里就已经被明确定义了,主要保障关系数据库ACID特性的I(Isolation),既对可能存在冲突的并发事务,提供一定程度的数据安全保证。一般来说数据库通过锁和MVCC来实现事务隔离级别,因此分析数据库的事务隔离级别就可以大致了解其锁和MVCC的实现。

一般来说,有四种最常见的事务隔离级别:1)读未提交Read UnCommitted;2)读提交Read Committed;3)重复读Repeatable Read;4)串行Serializable。

早期的数据库支持read uncommited,不过这种被称为脏读的事务是不安全的,对于现代的应用系统一致性要求无法满足,因此现代数据库产品几乎都不支持这种事务隔离级别了。

读提交是我们应用系统用到最多的一种事务隔离级别,不过现代数据库对于读提交有了更为严格的限制,当读发起的时间点,所有已提交的事务都是可见的,所有未提交的事务都是不可见的。为了实现这一点,数据库需要有一个严格单边增长的时间戳,这个时间戳和提交有严格的关系。凡是事务号低于这个时间戳的事务都是可见的,反之就是不可见的。Oracle把这个时间戳叫做SCN。读已提交隔离级别是SQL级别的,判断可见性是根据SQL执行的时间戳。

可重复读是更高的隔离级别,要求在一个事务中的多个读都是一致的。事务只能看到事务开始前已提交的数据。既不能看到未提交的数据,也不能看到事务在执行时被其它事务更新的已提交的数据。如果没有MVCC,这种隔离级别是十分低效的,不过有了MVCC,可以通过副本来避免阻塞写操作。

串行化是最高的事务隔离级别,也是最低效的一种,因为这要求并发事务串行执行。

绝大多数现代关系型数据库都支持READ COMMITTED事务隔离级别,这是我们应用最常用的方式。不过Repeatable Read在某些应用场景下也十分有用,可以大大简化应用开发。同时使用MVCC技术实现的repeated Read,其效率是最高的,比我们在应用中用锁去控制要高出数个数量级。

事务隔离级别是和并发事务,锁,MVCC相关。因此在数据库中评估事务隔离级别的实现方式与效率十分关键。Oracle数据库实际上只支持READ COMMITTED ,Serializable这两种ANSI定义的事务隔离级别,默认是READ COMMITED。而对于Read Committed,大家也不要简单的认为只要支持Read Committed的事务隔离级别,我的应用就可以肆无忌惮的把数据一致性都交给数据库了。实际上Oracle 的Read Committed和ANSI的Read Committed还是不同的,Oracle的Read Committed实际上是Read Consistency,一致性读。Oracle的读一致性是基于MVCC机制的,是使用类似SNAPSHOT的方式实现的,其实现基础是UNDO。因此实际上我们卡宴把Oracle的事务隔离级别看作是基于SQL语句的一致性读与基于事务的一致性读(Oracle版的Serializable,与ANSI的也是不同含义)。

MySQL是全部支持ANSI的四种事务隔离级别的,当然是不同的存储引擎下才完全支持这四种。MySQL的默认事务隔离级别是Repeatable Read,而不是Read Committed,这是因为5.X时代的BINLOG格式导致的,如果你用的是8.X,BINLOG使用ROW格式,那么最好还是调整为Read Committed。否则在有些应用场景下,死锁会比较多。大家要注意的是MySQL的Read Committed和Oracle的一致性读还是有一定区别的,这里篇幅有限,不做展开,大家有兴趣可以去查阅相关资料。

PostgreSQL也是完全支持ANSI的四种事务隔离级别的,只不过默认的事务隔离级别是Read Committed。PG实现事务隔离级别的方式有点类似与Oracle,都是基于Snapshot的,和MySQL的实现方式有些不同。

我们再来看看一些分布式关系型数据库的事务隔离级别情况。一般来说分布式数据库是通过2PC(或者变种的改进算法),GTM等机制来实现分布式事务和MVCC的。不同的数据库产品实现算法差异较大,因此分布式数据库在事务隔离级别上的差异还是很大的。我们先来看几个头部的国产分布式数据库的情况。时间有限,我们重点来看TIDB/OCEANBASE/HOTDB的事务隔离级别。

大家都知道,TIDB的SQL引擎是基于开源的MYSQL代码开发的,不过TIDB的事务隔离级别实现要比MYSQL复杂的多,不要用MYSQL的事务隔离级别来看TIDB的事务隔离级别。在理解Tidb的事务隔离级别的时候,一定要注意Tidb是支持乐观锁和悲观锁的。事务隔离级别在乐观锁模式下是不能起到ANSI SQL标准的隔离作用的。我们以下讨论Tidb的事务隔离级别都是基于悲观锁模式的。TiDB从4.0.0beta开始支持与Oracle基本类似的Read Committed隔离级别。从3.0.8开始,Tidb默认使用悲观锁,因此也引入了首个事务隔离级别SI,这是一个类似Oracle的一致性读,其读取基线为事务开始时,在整个事务中,事务隔离的特性类似与ANSI模型中的Repeatable Read。Tidb考虑了MySQL用户的使用习惯,因此也把这种SI隔离模式称为RR,只不过这个RR类似于Oracle 的Serializable,而不是MYSQL的RR。

OceanBase的企业版分为Oracle租户和MySQL租户两种模式,提供了对这两种数据库的兼容性支持。开源的社区版只有MySQL租户模式。

在两种租户模式下,OB的事务隔离级别时不同的。这是为了确保从这两种数据库中迁移应用过来时候的兼容性。OB是一种分布式数据库,因此其一致性读依赖于GTS(Global Timestamp Service),要想使用串行化事务隔离级别时,一定要确保GTS是开启的。

HotDB是通过MySQL提供的外部XA事务来实现分布式事务的强一致性的,因此HotDB的事务隔离级别的实现也是基于MySQL的XA接口的。HotDB支持Repeatable Read、SERIALIZABLE 隔离级别且隔离级别,并且功能表现和单机MySQL相同。通过HotDB实现分布式事务的方式我们也可以看出,与XA分布式事务相冲突的功能,在HotDB中也会收到一定的限制。

最后我们再来看看前阵子有朋友关心的GoldenDB的事务隔离级别问题。GoldenDB的分布式事务实现的方式比较独特,是通过一阶段提交外加自动补偿回滚的方式来实现的。没有严格的全局时间戳机制,只有一个实现Read Committed的全局事务控制机制。从实现原理上看,GoldenDB的Read Committed的实现进满足ANSI SQL 1992里最基本的要求,和Oracle的Read Consistency是不同的,当然Oracle并没有完全遵循SQL 1999中的标准,而是针对应用系统的要求,提升了RC隔离级别的一致性要求。不过大多数现代数据库产品都向Oracle靠齐,因此Oracle的RC实际上变成了RC隔离级别的标准了。一个Oracle数据库的应用向GoldenDB上迁移的时候,要十分关注这一点,否则可能会出现应用逻辑上的问题。

1
相关文章