为什么要使用READ UNCOMMITTED隔离级别?

270

用简单的英语来说,使用某个东西的优缺点是什么?

SET TRANSACTION ISOLATION LEVEL READ UNCOMMITTED

在.NET应用程序和报表服务应用程序中进行查询?

10个回答

249

这种隔离级别允许脏读。一个事务可以看到其他事务未提交的更改。

为了保持最高级别的隔离性,数据库管理系统通常会在数据上获取锁,这可能会导致并发性的降低和大量的锁定开销。而这种隔离级别放宽了此属性。

您可能还想查看关于 READ UNCOMMITTED 的维基百科文章,其中包括一些例子和进一步阅读材料。


您可能还有兴趣查看Jeff Atwood在早期Stack Overflow时如何解决死锁问题的博客文章。根据Jeff:

但是使用 nolock 会有危险吗?理论上可能会导致读取无效数据。你会发现,一些数据库架构专家开始向你解释ACID理论,告诉你在使用nolock时要小心谨慎,甚至告诉你要拉响建筑火灾警报。事实上,这些理论确实很可怕。但是,我认为:“在理论上,理论和实践没有区别,在实践中有。

我永远不会推荐使用nolock作为一般的“什么都能解决”的万能药来解决数据库死锁问题。您应该首先尝试诊断问题的根源。

但是,在实践中,只要您知道自己在做什么,向查询添加 nolock 从来没有导致问题... 只要您知道自己在做什么。

您可能要考虑的READ UNCOMMITTED级别的替代方案是READ COMMITTED SNAPSHOT。再次引用Jeff的话:

快照依赖于全新的数据更改跟踪方法...不仅仅是轻微的逻辑更改,它要求服务器在物理上处理数据的方式有所不同。一旦启用了这种新的数据更改跟踪方法,它就会创建每个数据更改的副本或快照。通过在争用时阅读这些快照而不是实时数据,读取操作不再需要共享锁,并且整体数据库性能可能会提高。


25
作者似乎表达的意思是read uncommitted / no lock将返回最后提交的任何数据。我的理解是,read uncommitted将返回最后设置的任何值,即使来自未提交的事务。如果是这样的话,结果不会只是检索“几秒钟过时”的数据,而可能会检索不存在或从未提交的数据(如果写入您读取的数据的事务被回滚)。我理解得对吗? - xr280xr
5
很好的回答。顺便说一下,自从我知道Oracle以来,它默认就有“快照”,可能比SQL Server引入它早几十年。当我开始使用SQL Server时,我感到非常失望,因为我发现所有并发问题都是使用“原始”的锁定机制解决的。在Oracle中从未见过“读未提交”。而从业者和宇航员一样幸福。 - Stefan Steinegger
22
如果使用READ UNCOMMITTED,你可能会重复读取某些行,或者漏掉整个行。如果在读取过程中出现页面拆分,则可能会丢失整块数据。只有当结果的准确性不重要时,才应使用WITH(NOLOCK) - Ian Boyd
12
@DanielNolan,建议阅读此文章是危险的,因为Jeff不知道他在做什么。Read-uncommitted(读未提交)仅适用于读取永远不会被修改的数据。尝试使用它来读取将要写入的表意味着你实际上将读取一个被回滚的内容。问题不仅在于你正在读取几秒钟之前的数据,而是......(原文缺失) - Pacerier
8
读取从未提交的数据是“脏读”的定义。如果你要基于这些未提交的读取结果进行写入,实际上你将会写入“脏数据”。文章还指出:“MySQL在Web应用程序中发展起来,因此其默认设置比SQL Server乐观得多。” 这并不正确,SQL Server 默认工作在“已提交读取”级别,而 MySQL 默认工作在“可重复读取”级别,距离“未提交读取”有5个级别之遥。 - Pacerier
显示剩余5条评论

66

我最喜欢使用读取未提交数据(read uncommited)的情景是用于调试事务内发生的问题。

在调试器下启动软件,当你逐行执行代码时,它会打开一个事务并修改您的数据库。在代码停止时,您可以打开查询分析器,将隔离级别设置为读取未提交数据并进行查询,以查看发生了什么。

您还可以使用它来查看长时间运行的过程是否卡住或使用count(*)查询正确地更新了数据库。

如果您的公司喜欢制作过于复杂的存储过程,那这个功能非常实用。


53

这对于查看长时间插入查询的进度、进行任何粗略估计(比如 COUNT(*) 或粗略的 SUM(*))等非常有用。

换句话说,脏读取查询返回的结果只要你将其视为估算值并且不基于它们做出任何关键决策,就是可以接受的。


32
优点是在某些情况下它可以更快。 缺点是结果可能不正确(尚未提交的数据可能会返回),并且没有保证结果可重复。
如果您关心准确性,请不要使用此选项。
有关更多信息,请参见MSDN

实现脏读或隔离级别0锁定,这意味着不会发出共享锁,并且不会尊重排他锁。 当设置此选项时,可以读取未提交或脏数据; 在事务结束之前,数据集中的值可以更改,行可以出现或消失。 此选项与在事务中所有SELECT语句的所有表上设置NOLOCK具有相同的效果。 这是四个隔离级别中最不限制的。


这会对查询速度产生什么影响? - Kip Real
11
"select"语句不需要等待获取被其他事务独占锁的资源的共享锁。 - Jarrod Dixon

25

什么情况下可以使用READ UNCOMMITTED

经验准则

好的情况:显示不断变化的总计的大型聚合报告。

有风险的情况:几乎所有其他情况。

好消息是,大多数只读报告都属于“好的”类别。

更多细节...

可以使用:

  • 几乎所有面向用户的当前非静态数据的聚合报告,例如年度销售额。它会冒一定的误差风险(可能小于0.1%),但比其他不确定性因素如输入错误或记录数据的随机性的风险要低得多。

这基本上涵盖了商业智能部门在SSRS中执行的大多数操作。当然,例外情况是任何带有$符号的内容。许多人在处理与服务客户并赚取利润所需的相关核心指标时,比处理货币更加热衷于财务方面的工作。(我责怪会计师)。

有风险的情况:

  • 任何到达详细级别的报告。如果需要该详细信息,则通常意味着每行都与决策相关。实际上,如果无法在不阻止的情况下拉取小的子集,则可能是因为正在编辑它。

  • 历史数据。这很少有实际影响,但由于用户了解到不断变化的数据无法完美,因此他们对静态数据的看法不同。脏读不会对此造成伤害,但是双重读取偶尔会发生。既然您本来就不应该在静态数据上进行阻塞,为什么要冒险呢?

  • 几乎所有供应具有写入功能的应用程序的内容。

即使是“好”的情况也不行。

  • 是否有任何应用程序或更新过程使用大型单个事务?其中删除然后重新插入许多记录?在这种情况下,您确实不能在这些表上为任何操作使用NOLOCK

关于报告的好点子。实际上,我脑海中浮现的第一个想法是:当用户查看一些UI网格并且数据准确性不是那么重要时,我是否应该在Web应用程序中使用“读取未提交”?用户只想快速了解可能存在哪些记录,也许还带有一些分页、排序和过滤功能。只有当用户点击编辑按钮时,我才尝试使用更严格的隔离级别读取最新的记录。这种方法在性能方面不应该更好吗? - JustAMartin
是的,我认为这很合理。请记住,更重要的问题是确保在点击编辑按钮和提交之间,数据没有被其他人更改。你可以通过启动事务,像select item from things with (UPDLOCK)这样获取数据来处理它。在那里放一个快速超时,以便如果无法快速获取锁定,它会告诉用户正在进行编辑。这不仅可以保护您免受用户的影响,还可以保护您免受开发人员的影响。唯一的麻烦是,您必须开始考虑超时以及如何在UI中处理它。 - Adamantish

8

关于报告,我们在所有报告查询中使用它,以防止查询拖慢数据库。我们之所以能够这样做,是因为我们正在获取历史数据,而不是最新的微秒级数据。


8

在源数据很少发生变化的情况下,使用 READ_UNCOMMITTED。

  • 读取历史数据时。例如,两天前发生的某些部署日志。
  • 再次读取元数据时。例如,基于元数据的应用程序。

在知道源数据可能在获取操作期间发生更改时,请勿使用 READ_UNCOMMITTED。


1
我觉得相反的情况适用。首先,静态数据应该可以无阻塞地读取。如果它确实阻塞了,那么你现在发现了一个重要的悬挂交易问题需要解决。此外,用户希望这与去年年度报告中打印出来的内容精确到最后一位小数点相匹配。他们通常不希望看到同样的报告处于不断变化之中。这并不适用于详细、极其时间敏感或财务报告,但如果1000个输入错误中有1个是可以容忍的,那么READ UNCOMMITTED也是可以接受的。 - Adamantish
简而言之:如果数据不会改变,那么您不需要使用READ UNCOMMITTED,因为根本没有块。如果您错了并且数据确实发生了更改,那么阻止用户获取比预期更脏的数据是一件好事。 - Adamantish
是的,我倾向于同意@Adamantish的观点 - 当您的数据正在被积极使用并且您想要减少服务器负载以避免可能的死锁和事务回滚时,您可以从“READ UNCOMMITTED”中受益,因为某些用户粗心地滥用带有数据网格的Web页面中的“刷新”按钮。通常同时查看一堆记录的用户不太在意数据是否过时或部分更新。只有当用户即将编辑记录时,您可能希望为他/她提供最准确的数据。 - JustAMartin

3
这将让你读到未提交的"脏"事务,这是最明显的答案。我认为仅仅为了加快读取速度而使用这种方法不是一个好主意。如果您使用良好的数据库设计,还有其他实现方式。
值得注意的是 READ UNCOMMITTED 不仅会忽略其他表锁,而且也不会在自己身上产生任何锁定。
考虑这种情况:您正在生成一个大型报告,或者您正在使用大而复杂的SELECT语句迁移数据出数据库。这将导致共享锁定,可能会升级为共享表锁定,直到事务完成。其他事务可以从表中读取,但无法更新。如果这是一个生产环境的数据库,这可能是个坏主意,因为整个系统可能会停止。
如果您使用READ UNCOMMITTED,则不会对表设置共享锁定。您可以从一些新事务中获取结果,也可能不会,具体取决于表中插入数据的位置以及您的SELECT事务读取数据的时间。如果例如发生页面拆分(数据将被复制到数据文件中的另一个位置),则您可能会多次获取相同的数据。
因此,如果对您来说很重要的是数据可在进行SELECT操作时插入,那么READ UNCOMMITTED可能是个不错的选择。您必须考虑到报告可能包含一些错误,但如果基于数百万行并且只有很少的行在选择结果时进行更新,那么这可能是“足够好的”。由于行的唯一性不能保证,您的事务也可能失败。
更好的方法可能是使用SNAPSHOT ISOLATION LEVEL,但是您的应用程序可能需要进行一些调整才能使用它。例如,如果您的应用程序对一行进行独占锁定以防止其他人阅读它并进入UI的编辑模式,则需要使用SNAPSHOT ISOLATION LEVEL。此外,SNAPSHOT ISOLATION LEVEL 也会带来相当大的性能损失(特别是在磁盘上),但是您可以通过增加硬件来克服这个问题。
您还可以考虑恢复数据库的备份并将其用于报告或将数据加载到数据仓库中。

0

它可以用于简单的表格,例如仅插入审计表中,在这种情况下,没有对现有行进行更新,也没有对其他表格的外键引用。插入是一个简单的插入操作,没有或很少可能回滚。


-6

我现在总是使用READ UNCOMMITTED。它速度快,问题最少。当使用其他隔离级别时,你几乎总会遇到一些阻塞问题。

只要你使用自动增量字段并稍微注意插入操作,那么你就不用担心阻塞问题了。

使用READ UNCOMMITED可能会出现错误,但老实说,确保你的插入操作是完全正确的非常容易。唯一需要注意的是从SELECT语句中获取结果的插入/更新操作(在这里使用READ COMMITTED,或确保脏读不会引起问题)。

所以,采用脏读(特别是对于大型报告),你的软件将运行更加顺畅...


7
这篇文章非常不准确,只是浅显地提到了使用 nolock 可能会遇到的问题。页面可能会分裂,连接可能会失败,你可能会读取不存在的数据或重复的数据。无法使其使用完全安全:在这种隔离级别下,你无法信任任何东西的准确性。READ COMMITTED SNAPSHOT 是一种较不危险的“假万能药”。 - Mark Sowul
@MarkSowul 对这个答案的负评似乎对我来说有点不公平。@Clive 明确表示他会在插入和更新时切换到“已提交”。 至于其他问题,他还提到了使用自增键来解决页面分裂问题,表明他已经意识到了这些问题。我同意他的观点,几乎所有为人类阅读而设计的实时报告都可以容忍最终小数位上的轻微差异。对于详细列表或将被机器读取和转换的数据,情况可能会有所不同,Clive 也持相同看法。 - Adamantish
2
这个评论也表明了对nolock可能出现的问题缺乏全面的理解。 "最后一位小数的轻微差异"几乎无法涵盖所有问题。即使涉及“仅针对插入/更新使用已提交读取”作为一般建议也是错误的(如果是“如果不存在则插入记录”怎么办?)。无论如何,“[未提交读取]速度快,问题最少”都是绝对错误的。 - Mark Sowul
记录一下,我同意你的回答,Adamantish:大致准确的聚合和其他很少的东西。 - Mark Sowul

网页内容由stack overflow 提供, 点击上面的
可以查看英文原文,
原文链接