一些人总当NOLOCK查询提示是SQL Server里的加速器,因为它避免了大量的死锁情景。在这篇文章里,我想向你展示下为什么NOLOCK查询提示是个不好的想法。

脏读(Dirty Reads)

NOLOCK查询提示一个最大的副作用就是在你的记录集会出现所谓的脏读这个事实。我们来看下面的代码:

 BEGIN TRANSACTION

 UPDATE Person.Person
SET FirstName = 'James'
WHERE LastName = 'Jones'

如你所见,我开始了一个新的事务,对AdventureWorks2012数据库里的Person.Person表进行UPDATE语句。现在当你尝试同时在另一个会话里读取这个记录时,这个SELECT语句会阻塞——在这个情况下请求的共享锁(S)会被已经授予的排它锁(X)阻塞——写阻塞读操作。

 SELECT
FirstName,
LastName
FROM Person.Person
WHERE LastName = 'Jones'
GO

一些人现在回应用SQL Server里的加速器,使用NOLOCK查询提示:

 SELECT
FirstName,
LastName
FROM Person.Person WITH (NOLOCK) -- It's a kind of magic...
WHERE LastName = 'Jones'
GO

如你所见死锁情景马上解决了,你从SQL Server返回记录了——遗憾的是在你面前现在有脏读(Dirty Read):你读取了尚未提交的数据。假设现在用UPDATE语句的第1个事务回滚了:在这个情况下,你已读取的数据在SQL Server里逻辑上是从未存在的。

为什么NOLOCK查询提示是个不明智的想法-LMLPHP

因此NOLOCK查询提示并不是在每个情景都是有用的。 如果你想运行一个报表,或者你只想快速返回结果,记录不用100%正确,这“可以是”个可取的(考虑下一天的平均销售额)。当然,当你需要精确的结果,NOLOCK并不可取。当然也有一些特定场景即使NOLOCK也会阻塞

读提交快照隔离(Read Committed Snapshot Isolation (RCSI))

有人给我提了为什么NOLOCK查询提示基本是一个不方便的(no-go)的选择:你不能在明显的方式里切换你的数据库查询到读提交快照隔离(Read Committed Snapshot Isolation (RCSI))。我从未想过这个情景,但没错是对的。读提交快照隔离(Read Committed Snapshot Isolation (RCSI))是个数据库选项。当你启用它的时候,每个查询都是在新的读提交快照乐观隔离级别里——只要在你的事务里不指定任何隔离级别。

对于特定的SQL语句使用NOLOCK查询提示,你临时修改隔离级别为未提交读(Read Uncommitted)。因此SQL语句也不会从读提交快照隔离受益,因为语句并没有在默认的提交读( Read Committed)隔离运行。这就对了!当你下次写下神奇的……WITH(NOLOCK)……时,好好想下这个额外参数。

小结

使用NOLOCK查询提示运行每个查询并不都有意义。一方面你会通过脏读(Dirty Reads)返回不一致的数据。另一方面你不能从读提交快照隔离(Read Committed Snapshot Isolation (RCSI))乐观隔离级别里受益,因为你临时修改了你SQL语句的默认隔离级别。

感谢关注!

参考文章:

https://www.sqlpassion.at/archive/2015/06/15/why-the-query-hint-nolock-is-a-bad-idea/

05-08 08:24