InfoQ

文章

Web开发必知的八种隔离级别

作者 James Leigh 译者 曹如进 发布于 2009年11月11日 上午2时18分

社区
Architecture
主题
性能和可伸缩性,
事务处理

ACID性质是数据库理论中的奠基石,它定义了一个理论上可靠数据库所必须具备的四个性质:原子性,一致性,隔离性和持久性。虽然这四个性质都很重要,但是隔离性最为灵活。大部分数据库都提供了一些可供选择的隔离级别,且现在许多库都增加了附加层来创建颗粒度更细的隔离。隔离级别应用范围如此之广主要是因为放宽隔离约束往往会使得可扩展性和性能提高几个数量级。

串行一致性是可用的最古老最高的隔离级别之一,它之所以倍受青睐是因为其提供的简单编程模型,即每次仅能有一个事务对给定的资源进行操作,这就避免了很多潜在的资源问题。尽管如此,大部分应用程序(尤其是Web应用程序)都不采用这种级别非常高的隔离,因为从终端用户的角度来看这是不切实际的-任何一个拥有大量用户群的应用程序在访问共享资源时都将会有几分钟的延迟,而这会使得用户量迅速减少。弱一致性和最终一致性在大规模分布式数据源中,例如Web中,随处可见。好几个成功的大型Web应用(例如,eBay和Amazon)都显示出乐观的(optimistic)弱一致性要比传统悲观的(pessimistic)机制在扩展性方面好得多。本文将一窥八种不同的隔离级别。学会适当的放宽数据一致性的约束,你可以在自己的应用程序中使用这八种隔离级别来获得更好的性能和可扩展性。

并发控制的主要目标是为了确保事务被隔离且不会影响到其他事务。要达到高级别的隔离需以牺牲性能为代价。并发控制可以用悲观或者乐观的机制来实现。大部分关系型数据库都使用了悲观机制来实现写入优化。悲观机制采用了锁,通过使用锁它可以阻塞一些操作或者进行某些形式的冲突检测。当一个表格,页面或是行被修改后,悲观机制中的锁可以用来阻塞其他潜在的访问修改资源的事务。然而,乐观机制并不采用任何锁,它仅仅依赖于冲突检测来维护事务隔离。乐观机制采用的冲突检测可以允许所有的读操作,并在事务结束时检验其一致性。如果检测到冲突,那么事务会进行回滚或重做。大部分web服务器都是读入优化,因此使用了乐观机制。通过允许所有的读入操作,乐观机制既可以保证很高的读写吞吐量,也可以在资源不是一直改变的情况下保证数据的一致性。

下面列出的隔离级别是用来帮助Web开发人员更好的理解他们编程模型中放置的约束,帮助系统架构师和开发人员共同讨论如何在保持必要的数据完整性的同时选择最有效的隔离级别。它们按照最少隔离(未提交读)到最多隔离(串行化)的顺序列出。

1、未提交读(Read Uncommitted)

未提交读隔离级别需要事务间很少的隔离。每一个读操作都能看到事务中等待的写操作(脏读)。然而已经提交的写操作必须要有一个串行顺序来防止脏写。悲观机制会阻塞有冲突的写操作直到其他写操作已经被提交或已经回滚。乐观机制不会锁住这些操作,它会允许所有的操作都通过。如果一个连接进行了回滚,那么接下来修改同一块数据的其他操作也会被回滚。在这种级别中,共享缓冲可以不加验证的进行使用。这种隔离级别最好在不需要事务(比如只读的数据集),或者事务只在独占数据库时才修改的情况下使用。

例子:一个只在离线情况下更新的档案数据库,或者不在事务中使用的审核/登陆(audit/logging)表。

2、已提交读(Read Committed)

已提交读可以读取系统中任何已经提交的状态,并且可以不加验证(混合状态)的进行缓冲,只需当前连接中发生的改变能够反映到结果中即可。悲观机制将其实现为单调视图。乐观事务则隔离存储所有的改动,使得它们直到提交后才可用。读已提交使用一个非常乐观的机制,它推迟写入所有的变化直到事务被提交为止。这种形式的乐观隔离可以在不阻塞读操作的情况下实现复杂的写入操作,并且它没有验证模式。共享缓冲只能在已提交的状态中使用。这种隔离级别最好在结果可以使用旧值,且事务只能用于写入操作的情况下使用。

例子:一个不必显示当前最新帖子的在线论坛,且它的帖子间数据不相冲突。

3、单调视图(Monotonic View )

单调视图是对读已提交的一个扩展,它其中的事务在执行时会观察数据库中一个单调上升的状态。在这种级别中,如果有明显的写入事务,那么悲观事务会在读入操作中被阻塞。乐观事务会像在读已提交中一样操作,隔离保存所有的改动,并且会验证它们的缓冲以确保其仍然合法。这种级别可以定期地同步数据库副本,且最好在不需要事务或者仅存在写操作事务的情况下使用。

例子:一个仅能由一个人来修改的用户偏好表。

4、快照读取(Snapshot Reads)

快照读取扩展了单调视图,它可以保证查询结果都能反映到数据库一致的快照中。悲观机制会在读操作时阻碍其他影响结果的写入操作。乐观机制则允许其他的写入操作,并通知读取事务某部分已经发生改变并进行回滚。想要实现一个乐观机制,必须在读操作结束之前验证是否有什么并行的写入操作修改了结果,如果有的话,那么结果可能会重做或回滚。这个检验过程可能只是简单的检查同一张表中是否出现了写入操作,或者只是检查改动的查询结果。乐观隔离级别可以很轻松地检测出冲突,并且在允许并发读入操作的过程中,支持写入操作。这种级别只要能够读取到快照,便可以定期地同步数据库副本。最好在写入操作很少,不想与读入操作冲突,且查询结果需要一致性的时候使用这种隔离级别。

例子::一个查询比修改频繁,且只保留最新值的货币换位表或者查询表。

5、游标稳定性(Cursor Stability)

游标稳定性隔离扩展了读已提交,并且是许多关系型数据默认的隔离级别。在这种隔离级别中,悲观事务如果在一个单独的语句中执行的话,必须得指定它将修改的记录。这通常可以在"SELECT"查询后附加“FOR UPDATE”关键字来实现。在这种情况下,其他冲突的读写悲观事务都将被阻塞直到该事务结束为止。乐观事务会跟踪提交时被验证的所有修改记录/实体的版本号。这是一种很流行的乐观隔离级别,因此被所有的主流对象关系映射库支持。在Java持久性API中,可以使用FLUSH_ON_COMMIT(尽管查询可能不影响本地改动)来接近达到这种级别,且如果检测到冲突的话,可以抛出OptimisticLockException 异常。这种隔离也同样可以用在HTTP头域的If-Match或者 If-Unmodified-Since中,它可以用来在更新前对比上一个资源的版本或者时间戳。这种级别最好在实体由外部信息(不从数据库中读取)更改,或者改动不会彼此覆盖的情况下使用。

例子:一个共享的公司目录或者一个wiki。

6、可重复读取(Repeatable Read)

可重复读取级别扩展了游标稳定性,它保证事务内的任何数据在事务过程中都不会被修改或者移除。悲观事务需要读取所有记录上的锁,并阻塞其他服务来修改这些记录。乐观事务则会跟踪所有的记录或者实体,并检查它们是否在提交时被修改过。这种级别最好在实体状态能够影响其他实体,或者事务由读写操作构成的情况下使用。

例子:一个订单跟踪数据库,它从一个实体中读取值并用它来计算其他的实体值。

7、快照隔离(Snapshot Isolation)

快照隔离扩展了快照读取和可重复读取,它保证事务中所有进行的读操作都能看到数据库中一致的快照。事务执行的的任何读操作都会有相同的结果,而不管它们在事务中执行的早晚。这和可重复读取不同,因为快照隔离能够防止幻读(查询结果不断变化)。许多关系型数据库采用多版本并发控制(也可以叫做 SERIALIZABLE)来支持这种级别,实现方法是通过锁和冲突检测的组合。在这种级别中,考虑到它可能与悲观机制或者乐观机制相冲突,因此事务一定要做好回滚的准备。悲观机制会通过锁住资源来尝试减少冲突的机会,但是必须在事务提交后将这些改动合并。乐观机制也会使用多版本并发控制,但是它不会阻塞其他可能产生潜在冲突操作的事务,反而是将冲突的事务进行回滚。这种级别的隔离最好在事务可以读取和修改多个记录的情况下使用。

例子:一个基于系统状态规则的工作流系统。

8、可串行性(Serializability)

串行性是快照隔离的扩展,它要求所有的事务都必须一个接着一个的出现,就好比它们被串行化过一样。悲观机制需要锁住所有评估过的查询,以防止写入操作影响这些结果。而乐观机制则跟踪所有评估过的查询,并在事务结束时使用一个后向验证或前向验证的模式来检查是否有并行写入操作影响了并行读入操作,如果有的话,它会将冲突事务外的所有事务进行回滚。在这种隔离级别中,任何提交事务都不会改变系统的表征状态。最好在需要完整数据一致性的情况下使用这个级别的隔离。

例子:一个进行范围查询来计算新值的账目系统。

总结

下面是本文提到的隔离级别的汇总表,它可以帮助你找到最适合你应用程序的级别。

事务在不同隔离级别中可能的冲突类型:

  脏写 脏读 混合状态 不一致读 覆写 不可重复 幻读 不一致性
未提交读 不可以 可以 可以 可以 可以 可以 可以 可以
已提交读 不可以 不可以 可以 可以 可以 可以 可以 可以
单调视图 不可以 不可以 不可以 可以 可以 可以 可以 可以
快照读取 不可以 不可以 不可以 不可以d 可以 可以 可以 可以
游标稳定性 不可以 不可以 可以 可以 不可以 可以 可以 可以
可重复读取 不可以 不可以 可以 可以 不可以 不可以 可以 可以
快照隔离 不可以 不可以 不可以 不可以 不可以 不可以 不可以 可以
可串行性 不可以 不可以 不可以 不可以 不可以 不可以 不可以 不可以

不同隔离级别的最佳前提:

  缓冲 数据同步 乐观冲突模式 建议操作 例子
未提交读 允许缓冲 间歇的 检测脏写 不能并发读写 档案
已提交读 允许缓冲 间歇的 没有冲突检测 单调的读/写 Web论坛
单调视图 必须被验证 周期的 没有冲突检测 组合读入 用户偏好
快照读取 必须被验证 周期的 对比读入与修改内容 一致性读入 查询表
游标稳定性 允许缓冲 间歇的 对比修改的实体版本 CRUD服务 目录
可重复读取 允许缓冲 间歇的 对比读入的实体版本 读/写实体 订单跟踪
快照隔离 必须被验证 周期的 对比读入的实体版本 同步实体 工作流
可串行性 必须被验证 完整同步 对比查询与修改内容 完善数据一致性 账目

数据一致性在数据库应用程序中至关重要-它允许开发者在分布式环境下使用数据。尽管强一致性级别如可串行性提供了一个简单的编程模型,但是它们会导致开销 过大,操作阻塞或者事务回滚,这对于很多应用程序来说都是不必要的。如果有其他问题的话,可以使用更加适当的隔离级别来帮助开发人员和系统架构师,让他们 在保持性能和开销平衡的前提下更好的理解数据一致性的需求。

查看英文原文:Eight Isolation Levels Every Web Developer Should Know


感谢霍泰稳对本文的审校。

给InfoQ中文站投稿或者参与内容翻译工作,请邮件至editors@cn.infoq.com。也欢迎大家加入到InfoQ中文站用户讨论组中与我们的编辑和其他读者朋友交流。

受教了,很不錯的總結! 发表人 Timothy Song 发表于 2009年11月11日 下午7时0分
不想多说什么 发表人 Karl MA 发表于 2009年11月12日 上午1时13分
good job 发表人 Gavin Zhang 发表于 2009年11月12日 上午8时44分
好。 发表人 Qin Chuan 发表于 2009年11月14日 上午12时30分
good 发表人 li dg 发表于 2009年11月17日 下午7时8分
写的真好 发表人 nie chao 发表于 2009年11月17日 下午7时23分
文章质量相当的高 发表人 akan dfxs 发表于 2009年11月19日 下午7时46分
总结很好 发表人 方招 李 发表于 2009年11月24日 上午12时24分
  1. 返回顶部

    受教了,很不錯的總結!

    2009年11月11日 下午7时0分 发表人 Timothy Song

    如題...

  2. 返回顶部

    不想多说什么

    2009年11月12日 上午1时13分 发表人 Karl MA

    不想多说什么,非常好的总结。感谢。

  3. 返回顶部

    good job

    2009年11月12日 上午8时44分 发表人 Gavin Zhang

    很感谢

  4. 返回顶部

    好。

    2009年11月14日 上午12时30分 发表人 Qin Chuan

    很实用的总结。省了我很多功夫。

  5. 返回顶部

    good

    2009年11月17日 下午7时8分 发表人 li dg

    总结的很好!

  6. 返回顶部

    写的真好

    2009年11月17日 下午7时23分 发表人 nie chao

    写的真好,希望infoq能多翻译一些这类文章~~

  7. 返回顶部

    文章质量相当的高

    2009年11月19日 下午7时46分 发表人 akan dfxs

    多谢多谢。

  8. 返回顶部

    总结很好

    2009年11月24日 上午12时24分 发表人 方招 李

    web开发这么长时间了,很有必要对自己的思想作这么一个疏导。受教了。

深度内容

模块化Java:声明式模块化

本文是模块化Java系列文章的第4篇,介绍的是声明式模块化。文中描述了组件如何以声明的方式来定义并组织在一起,而无需让代码依赖于OSGI API。

Ian Robinson和Jim Webber谈论基于Web的整合

本采访是在伦敦举行的QCon2009上记录的,Ian Robinson和Jim Webber探讨了如何将Web作为整合平台以及REST在理论上和实践中的好处。

项目管理修炼之道(精选版)

项目管理对于项目成败至关重要,但实践中每个项目都有自己的独特性,没有现成的解决方案可以套用。书中从应对实际风险的角度出发,讲述了从项目启动、项目规划到项目结束的整个管理流程,展示了作者的思考过程。本迷你书从原书中精选出5个章节。

那是鸟,还是飞机?不,那是超人!

在这个演讲中,Fred将会揭示敏捷的一些外在因素,并会重点关注敏捷获得成功的内在原因。从案例研究和真实的项目经验来看,Fred认为:工具、管理体系都不能让你变得敏捷。敏捷的成功,植根于士气高涨、充分授权的工作者身上,他们能够以不同以往的方式思考问题。

访谈和书摘:Eben Hewitt的新书《Java SOA Cookbook》

Java SOA Cookbook

Eben Hewitt的新书《Java SOA Cookbook》从Java实现的角度讨论了面向服务架构。Eben在书中讨论了SOA基础、工具、最佳实践和SOA治理等主题。

Mark Richard的《Java消息服务》第二版

Mark Richards的新书《Java消息服务》第二版覆盖了JMS的许多主题, 包括发布和订阅模式以及点对点模式,消息过滤和事务等。InfoQ与Mark谈论了跟他的新作。

模块化Java:动态模块化

本文是“模块化Java”系列文章的第三篇,讨论动态模块化,内容涉及如何解析bundle类、bundle如何变化、以及bundle之间如何通信。

让测试也敏捷起来

对于测试组织来说,敏捷方法带来的快速迭代却让测试本身变得困难起来:缺乏“足够详细的文档”,缺乏“仔细设计用例的时间”等等。在本演讲中,段念将与大家探讨如何在敏捷过程中进行测试。