速来报名!AICon北京站鸿蒙专场~ 了解详情
写点什么

事务隔离级别和脏读的快速入门

  • 2016-11-14
  • 本文字数:4380 字

    阅读完需:约 14 分钟

关键要点

  • 仅从 ACID 或非 ACID 角度考虑问题是不够的,你应知道你的数据库支持何种事务隔离级别。
  • 一些数据库宣称自己具有“最终一致性”,但却可能对重复查询返回不一致的结果。
  • 相比于你所寻求的数据库,一些数据库提供更高的事务隔离级别。
  • 脏读可导致同一记录得到两个版本,或是完全地丢失一条记录。
  • 在同一事务中多次重新运行同一查询后,可能会出现幻读。

最近 MongoDB 登上了 Reddit 的头条,因为 MongoDB 的核心开发者 David Glasser 痛苦地认识到 MongoDB 默认会执行脏读

在本文中,我们将解释什么是事务隔离级别和脏读,并给出一些广受欢迎的数据库是如何实现它们的。

ANSI SQL 给出了四种标准的事务隔离级别:可序列化 (Serializable)、可重复读 (Repeatable reads)、提交读 (Read committed) 和未提交读 (Read uncommitted)。

许多数据库缺省是提交读的,这保证了在事务运行期间用户看不到转变中的数据。提交读的实现通过在读取时暂时性地获取锁,并持有写入锁直至事务提交。

如果在一个事务中需要多次重复同一读取,并想要“合理地确定”所有的读取总是会得到同样的结果,这要在整个过程期间持有读取锁。在使用可重复读事务隔离级别时,上述操作是自动完成的。

我们这里所说的“合理地确定”可重复读,是因为存在“幻读”(phantom reads)的可能性。当执行使用了 WHERE 语句的查询时,类似于“WHERE Status=1”,就有可能发生幻读。虽然所涉及的行将被锁上,但是这并不能阻止匹配 WHERE 条件的新行被添加进来。“幻”(phantom)一词指在查询第二次执行时所出现的行。

为确保在同一事务中的两次读取会返回同样的数据,可使用可序列化事务隔离级别。可序列化使用了“范围锁”,避免了匹配 WHERE 条件的新行添加到一个开放的事务中。

一般情况下,由于锁竞争的存在,事务隔离级别越高,性能越差。因此为了改进读取性能,一些数据库还支持未提交读。该事务隔离级别将无视锁的存在(事实上其在 SQL Server 中被称为“NOLOCK”),因此该级别下可执行脏读。

脏读所存在的问题

在探讨脏读问题之前,你必须要理解表并非是真实存在于数据库中的,表只是一个逻辑结构。事实上你的数据是按一个或多个索引进行存储的。主索引在大多数数据库中被称为“聚束索引”或“堆”(该术语在各 NoSQL 数据库中各不相同)。因而当执行插入操作时,需要在每个索引中插入一行。当执行更新操作时,数据库引擎仅需访问指到被改变列的索引。但更新操作常常必须要在每个索引上执行两个操作,即从旧的位置删除并在新的位置插入。

在下图中,你可看见一个普通的表,还有表中 IX_Customer_State 和 PK_Customer 对象更新操作的执行计划。鉴于表的 FullName 列并未改变,所以可以跳过 IX_Customer_FullName 索引。

(点击放大图像)

注意在SQL Server 中,PK 前缀指代主键,通常也是用于聚束索引的键。IX 用于指代非聚束索引。其它的数据具有它们自己的命名规范。

解决了上述问题,让我们看一下脏读导致不一致数据的多种途径。

未提交读问题易于理解。在事务被完全提交之前,如果无视写入锁的存在,使用“未提交读”的SELECT 语句就可以就看到新插入或更新的行。如果这些转变操作这时被回滚,从逻辑上说,SELECT 操作将返回并不存在的数据。

如果数据在更新操作过程中被移动了,这就产生了双重读取。例如,你正在读取所有的客户记录的状态。如果在你读取“California”记录和读取“Texas”记录之间,上面所说的更新语句被执行了,你就能看见“客户1253”记录两次。一次是旧值,一次是新值。

记录丢失发生的方式相同。如果我们提取“客户1253”记录并将其从“Texas”记录移动到“Alaska”记录,并再次使用状态去选择数据,你可能会完全地丢失该记录。这就是发生在David Glasser 的MongoDB 数据库中的事情。由于在更新操作期间读取了索引,查询丢失了记录。

脏读也会妨碍到排序操作,该问题的出现取决于数据库的设计方式及特定的执行计划。例如,脏读可能发生于执行计划对所有候选数据行采集指针信息时,如果在其后一行数据被更新了,但实际上执行引擎还是会使用已被采集的指针信息从原始位置拷贝数据。

快照隔离,或被称为“行级版本控制”

为在避免脏读问题的同时提供好的性能,许多数据库支持快照隔离语义。运行于快照隔离状态下,当前的事务不能看到任何先于其启动的其它事务的结果。

快照隔离的实现是通过做被改变行的临时拷贝,而非仅依靠于锁机制,因此它也常被称为“行级版本控制”。

很多支持快照隔离语义的数据库在被请求使用“提交读”事务隔离时,会自动使用快照隔离。

SQL Server 中的事务隔离级别

SQL Server 支持所有四种 ANSI SQL 事务隔离级别,外加一种显式的快照隔离级别。提交读可能也使用快照语义,这取决于数据库中 READ_COMMITTED_SNAPSHOT 选项的配置方式。

在开关该选项前,你的数据库需要做充分的测试。虽然提交读可以提升读取性能,但它也同时降低了写入性能。尤其是 tempdb 被部署在慢速磁盘上时,因为这存储了行的旧版本。

在 SELECT 语句中可以使用臭名昭著的 NOLOCK 指示符。NOLOCK 的作用等同于将事务运行设置为未提交读。这在 SQL Server 2000 及更早期的版本中被大量地使用,因为那时并没有提供行级版本控制。尽管现在不再必要或不建议这样做,但是该习惯仍然保留着。

更多信息参见“设置事务隔离级别 (Transact-SQL) ”.

PostgreSQL 中的事务隔离级别

虽然官方宣称 PostgreSQL 支持所有四种 ANSI 事务隔离级别,但事实上 PostgreSQL 中只有三种事务隔离级别。每当查询请求“未提交读”时,PostgreSQL 就默默地将其升级为“提交读”。因此 PostgreSQL 不允许脏读。

当你选取“未提交读”级别时,事实上你得到了“提交读”,在 PostgreSQL 对可重复读的实现中,脏读是不可能发生的,因此实际的事务隔离级别可能比你所选取的要更加严格。这是被 SQL 标准所允许的,因为四种事务隔离级别仅定义了事务中一定不能发生的现象,它们并未定义应该发生哪种现象。

PostgreSQL 并未显式地提供快照隔离。当然快照隔离是在使用提交读时自动发生的。这是因为 PostgreSQL 的设计从一开始就考虑了多版本并发控制

在9.1 版本之前,PostgreSQL 不提供可序列化事务,会将它们静默降级为可重复读。但当前所有仍在支持的PostgreSQL 版本中都不再有这个限制了。

更多的信息参见PostgreSQL 官方文档的 13.2 节,“ 事务隔离”.

MySQL 中的事务隔离级别

InnoDB 默认为可重复读,但是提供所有四种 ANSI SQL 事务隔离级别。提交读使用快照隔离语义。

更多 InnoDB 相关的信息,参见 MySQL 官方文档的 15.3.2.1 节“ 事务隔离等级”

事务在使用 MyISAM 存储引擎时是完全不被支持的,这里使用了表一级的单一读写锁(虽然在某些情况下,插入操作是可以绕过锁的。)

Oracle 中的事务隔离等级

Oracle 只支持三种事务隔离级别,即提交读、可序列化和只读。在 Oracle 中,提交读是默认的,它使用快照语义。

类似于 PostgreSQL,Oracle 并不提供未提交读,永不允许脏读。

可重复读并不在 Oracle 的支持列表中。如果你需要在 Oracle 中具有该行为,你的事务隔离级别需要被设置为可序列化。

只读是 Oracle 所独有的事务隔离级别。但是对此并没有很好的文档,手册中只有如下描述:

只读事务只能看见那些在事务开始阶段就被提交的改变,不允许 INSERT、UPDATE 和 DELETE 语言。

对其它两种事务隔离级别的更多信息,参见 Oracle 官方文档第13 章“数据并发和一致性”

DB2 中的事务隔离级别

DB2 具有四种隔离级别,分别称为可重复读、读稳定性、游标稳定性和未提交读。这四种级别并不与上述四种 ANSI 术语一一对应。

可重复读对应于 ANSI SQL 中的可序列化,意味着不可能存在脏读。

读稳定性对应于 ANSI SQL 中的可重复读。

游标稳定性用于提交读,是 DB2 的默认设置配置。对于 9.7 版快照语义生效。而在 9.7 的前期版本中,DB2 使用类似于 SQL Server 的锁机制。

未提交读在很大程度上类似于 SQL Server 中的未提交读,也允许脏读。手册中推荐仅在只读表上使用未提交读,或是用在“可以看到未被其它应用提交的数据时”。

更多信息参见“事务隔离级别”。

MongoDB 中的事务隔离级别

正如前文所提到的,MongoDB 不支持事务。在其手册中对此是这样描述的:

因为在 MongoDB 中对单一文档的操作是原子的,两阶段提交只能提供类事务语义。在两阶段提交或回滚期间,应用可在中间点返回中间数据。

事实上这意味着 MongoDB 使用脏读语义,具有双倍或丢失记录的可能性。

CouchDB 中的事务隔离等级

CouchDB 也不支持事务。但是不同于 MongoDB 的是,它使用了多版本并发控制去避免脏读。

读取请求将总是在请求开始时就能看到数据库的最新快照。

这所给予 CouchDB 的事务隔离等级,等价于具有快照语义的提交读。

更多的信息参见“最终一致性”。

Couchbase Server 的事务隔离级别

Couchbase Server 常被混淆为 CouchDB,但它是一种完全不同的产品。就索引而言,它并未提供任何形式的隔离。

当执行更新操作时,Couchbase Server 仅更新主索引,或称其为“真实的表”。所有的二级索引将被延迟更新。

虽然在 Couchbase Server 文档并没有明确说明,看上去它在构建索引时使用了快照,如果确是如此,脏读应该不成为问题。但是由于索引的延迟更新,在 Couchbase Server 中仍不能获得真正的提交读事务隔离级别。

和许多的 NoSQL 数据库一样,Couchbase Server 并不直接支持事务。但是你确实可以使用显式锁,但锁只能在被自动丢弃前维持 30 秒的时间。

更多的信息参见“对条目上锁”、“你所应知道的关于Couchbase 架构的所有事情”和“ Couchbase 视图引擎的内幕”。

Cassandra 中的事务隔离级别

Cassandra 1.0 隔离了甚至是对一行的写入操作。因为字段是被逐一更新的,所以可以终止对旧值和新值混合在一起的记录的读取。

从 1.1 版本开始,Cassandra 提供了“行级隔离”。这让 Cassandra 具有等同于其它的数据库中被称为“未提交读”的隔离级别。Cassandra 并未提供更高级别的隔离。

更多的信息参见“关于事务和并发控制”。

了解你的数据库的事务隔离级别

正如从上述实例中可看到的,仅从ACID 和非ACID 角度考虑你的数据库是不够的。你的确需要去知道你的数据库应在何种情况下支持何种的事务隔离级别。

关于作者

Jonathan Allen的首份工作是在上世纪九十年代末做诊所的 MIS 项目,Allen 将项目逐步由 Access 和 Excel 升级到企业级的解决方法。在从事为财政部门编写自动交易系统代码的工作五年之后,他成为项目顾问,参与了包括机器人仓库 UI、癌症研究软件中间层、主要房地产保险企业的大数据需求等在内的各种行业项目。在闲暇时间,他喜欢研究源于 16 世纪的武术,并为其撰写文章。

查看英文原文: A Quick Primer on Isolation Levels and Dirty Reads


感谢冬雨对本文的审校。

给InfoQ 中文站投稿或者参与内容翻译工作,请邮件至 editors@cn.infoq.com 。也欢迎大家通过新浪微博( @InfoQ @丁晓昀),微信(微信号: InfoQChina )关注我们。

2016-11-14 17:585672
用户头像

发布了 227 篇内容, 共 74.8 次阅读, 收获喜欢 28 次。

关注

评论

发布
暂无评论
发现更多内容

火山引擎边缘云出席2024亚太内容分发大会,助推游戏体验全面升级

火山引擎边缘云

CDN 游戏 CDN加速 CDN带宽

数字化先行者,HAP大使招募中

明道云

在 ASP.NET Core 引入 Scrutor 增强依赖注入

雄鹿 @

ASP.NET Core

Visual Studio 2022 内置 Git 异常

雄鹿 @

Visual Studio 2022

[自研开源] 数据集成之分批传输 v0.7

LIEN

开源 数据集成 业务融合 API对接 mydata

单体分层应用架构剖析

疯狂架构

架构 分层架构 单体应用架构

分享10个学习计划模板,让你的个人发展更上一层楼!

彭宏豪95

效率工具 在线白板 模板 办公软件 学习计划

在 ASP.NET Core 引入 Autofac 增强容器

雄鹿 @

ASP.NET Core

php所有函数总结

百度搜索:蓝易云

php 云计算 Linux 运维 云服务器

ARM版Win10系统下载-Windows系统下载

Rose

win10 Windows系统 win 10镜像

C++ 用户输入与数据类型详解:建立基本计算器及变量类型

小万哥

程序人生 编程语言 软件工程 C/C++ 后端开发

深入理解HTTP请求的五个要点

百度搜索:蓝易云

云计算 Linux 运维 HTTP 云服务器

哪里有Mac SVN管理工具 cornerstone 4破解版?

Rose

SVN管理工具 cornerstone 4 破解版 cornerstone 4许可

有一个可以写到简历的项目,嘎嘎强!

冰河

分布式 微服务 高并发 聊天 IM即时通讯

Django数据库类库MySQLdb使用详解

百度搜索:蓝易云

django Linux 运维 云服务器 mysqldb

AlDente Pro电池寿命延长工具功能介绍 及 AlDente Pro使用教程

Rose

Mac软件 AlDente Pro破解版 AlDente Pro电池寿命延长

YYDS,只用几条命令轻松搭建自己的项目管理平台jira

霍格沃兹测试开发学社

使用 Amazon Bedrock + Claude 3 打造个性化智能编程助手

亚马逊云科技 (Amazon Web Services)

人工智能

StarRocks 助力小红书离线数仓提效,提升百倍回刷性能!

StarRocks

数据库 大数据 数仓 湖仓一体

STL算法大全

百度搜索:蓝易云

Linux 运维 云服务器 stl C+

Xliff Editor for Mac 编辑和管理XLIFF文件

Rose

Mac软件 Xliff Editor XLIFF文件

jprofiler安装使用教程 附jprofiler永久激活码 Mac/win

Rose

Java性能 JProfiler激活码 JProfiler 14下载

一口气搞懂分库分表 12 种分片算法,大厂都在用

程序员小富

Java 分库分表 spring-boot

优秀电源工程师需要的必备技能

梦笔生花

工程师 电源 优秀

事业-最佳实践-编码-继承组合选择

南山

最佳实践 组合模式 设计原则 组合 继承

《出海和跨境:明道云HAP支撑全球化业务的能力白皮书》正式发布

明道云

编写的Snort规则不报警怎么办?

百度搜索:蓝易云

云计算 Linux 运维 云服务器 Snort

就是这么简单,Selenium StaleElementReferenceException 异常分析与解决

霍格沃兹测试开发学社

Python中的datetime模块:轻松拿捏时间操作

霍格沃兹测试开发学社

ASP.NET Core 依赖注入(Ioc)

雄鹿 @

ASP.NET Core

Acrobat Pro DC 2023中文直装版 PDF编辑软件

Rose

Acrobat Pro DC 2023下载 PDF编辑和管理

事务隔离级别和脏读的快速入门_架构_Jonathan Allen_InfoQ精选文章