写点什么

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

  • 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:585621
用户头像

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

关注

评论

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

好的测试用例应该具备哪些特质

老张

软件测试 质量保障 测试用例 研发管理工具

电子阅读软件中文版MarginNote 3 for Mac

Mac相关知识分享

强大全景图拼接制作工具PTGui Pro for Mac

Mac相关知识分享

Mac软件 图像制作

谷歌访问助手插件 for Mac中文版

Mac相关知识分享

谷歌插件

高性能无锁队列 Disruptor 核心原理分析及其在i主题业务中的应用

vivo互联网技术

Disruptor 无锁 伪共享 内存队列 CPU Cache

大促高并发系统性能优化实战--京东联盟广告推荐系统

京东零售技术

算法 数据 企业号2024年8月PK榜

零信任SDP更新 卓越性能全新体验

芯盾时代

iam 统一身份认证 零信任 sdp

京东秒送LBS场景下的容灾数据备份方案

京东零售技术

架构 数据 LBS 企业号2024年8月PK榜

陶建辉在第 41 届中国数据库学术会议上深度解析时序数据库

TDengine

LED显示屏批发需要注意的关键事项

Dylan

LED显示屏 全彩LED显示屏 led显示屏厂家 市场 采购

强大 SSH 工具Termius mac 版

Mac相关知识分享

讯兔科技携手豆包大模型,勇闯AI投研“深水区”

新消费日报

观测云的成本效益分析:开源工具在企业监控中的应用

可观测技术

开源

科大讯飞学习机s30和p30区别对比

妙龙

科大讯飞 学习机

LeetCode题解:1797. 设计一个验证系统,哈希表,JavaScript,详细注释

Lee Chen

LeetCode题解:1234. 替换子串得到平衡字符串,滑动窗口,详细注释

Lee Chen

LLM活动 | 与UP主“老陈打码”一起使用PAI×LLaMA Factory搭建AI诸葛亮

阿里云大数据AI技术

人工智能 阿里云 AIGC LLM PAI

得物Flink内核探索实践

得物技术

flink 技术分享 企业号2024年8月PK榜

超越传统:观测云的监控功能与 AI 集成

可观测技术

人工智能

构建业务可观测性:统一数据结构的重要性

可观测技术

数据结构

大模型加持,火山引擎数据飞轮转入消费行业

字节跳动数据平台

大数据 数字化转型 云服务 数据平台 火山引擎

持续更新与技术领先:保持监控系统的先进性

可观测技术

产品迭代

京东集团项目管理人才发展通道代表受邀参加第三届中国PMO&PM大会

京东零售技术

企业号2024年8月PK榜

亚马逊Amazon商品详情API接口(主图|SKU|标题|价格|库存)

tbapi

亚马逊 亚马逊商品详情接口 亚马逊API接口 亚马孙商品数据采集

数据分析统计软件Minitab Express for Mac激活版

Mac相关知识分享

数据分析 Mac软件

QCN974/QCN6274 Chip multi-band network adapter configuration details - Innovation in the age of Wi-Fi 7

wifi6-yiyi

WiFi7

基于51单片机设计的计算器

DS小龙哥

8月月更

没有它,你的Scrum无法实现!

敏捷开发

Scrum 敏捷开发

Remote Write:观测云数据存储的新篇章

可观测技术

Prometheus

2024英特尔中国学术峰会:聚焦绿色计算,推动产学融合

E科讯

使用PAI × LLaMA Factory 微调 Llama3 模型

阿里云大数据AI技术

人工智能 模型训练 LLM PAI

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