在这种情况下,READ UNCOMMITTED / NOLOCK是否安全?

时间:2010-06-08 21:43:05

标签: sql-server sql-server-2005 deadlock read-committed-snapshot

我知道快照隔离可以解决这个问题,但是我想知道在这种特殊情况下NOLOCK是否安全,这样我就可以避免开销。

我有一张看起来像这样的表:

drop table Data

create table Data
(
    Id BIGINT NOT NULL,
    Date BIGINT NOT NULL,
    Value BIGINT,
    constraint Cx primary key (Date, Id)
)

create nonclustered index Ix on Data (Id, Date)

表格没有更新。删除可能会发生,但它们永远不应该与SELECT竞争,因为它们会影响表的另一个较旧的末尾。插入是常规的,并且(Id,Date)索引的页面拆分非常常见。

我在标准INSERT和SELECT之间出现死锁情况,如下所示:

select top 1 Date, Value from Data where Id = @p0 order by Date desc

因为INSERT获取Cx(日期,Id;值)然后Ix(Id,日期)的锁定,但SELECT获取Ix(Id,Date)上的锁定然后Cx(日期,Id;值) 。这是因为SELECT首先寻找Ix然后加入到Cx上的搜索。

交换聚簇索引和非聚簇索引会破坏此循环,但这不是一个可接受的解决方案,因为它会引入其他(更复杂的)SELECT循环。

如果我将NOLOCK添加到SELECT中,在这种情况下会出错吗?可以回复:

  1. 不止一行,即使我要求TOP 1?
  2. 没有行,即使存在并且已经提交了吗?
  3. 最糟糕的是,一行不满足WHERE子句?
  4. 我已经在线阅读了很多这方面的内容,但我看到的(onetwo)过度或不足数异常的复制涉及扫描。这只涉及寻求。 Jeff Atwood has a post关于使用NOLOCK进行了很好的讨论。我对Rick Townsend的评论特别感兴趣:

      

    其次,如果你读了脏数据,那么   你跑的风险就是阅读   完全错误的排。例如,如果   您的选择读取要查找的索引   你的行,然后更新更改   行的位置(例如:由于a   页面拆分或更新   聚集索引),当你选择   去读实际的数据行,就是这样   要么不再存在,要么不同   一共排!

    这是否可以仅使用插入,而且没有更新?如果是这样,那么我想即使是我对仅插入表的搜索也可能是危险的。


    更新

    我想弄明白how snapshot isolation works。它似乎是基于行的,其中事务读取表(没有共享锁!),找到他们感兴趣的行,然后看看他们是否需要从tempdb中的版本存储中获取旧版本的行。

    但在我的情况下,没有行会有多个版本,因此版本存储似乎毫无意义。如果找到没有共享锁的行,那么仅使用NOLOCK有什么不同?

2 个答案:

答案 0 :(得分:7)

使用NOLOCK或READ UNCOMMITTED意味着您放弃任何一致性保证。周期。

如果您需要一致性,请不要进行脏读。您的整个解释依赖于未记录的行为,可能会在将来的版本中发生变化,更糟糕的是依赖于您希望查询的特定访问计划。查询优化器可以自由选择它认为合适的任何计划,并且您在生产中可能会破坏您所做的任何假设。所以回到原点:如果你不准备面对后果,不要做脏读。

不确定这是否适用,不清楚您尝试使用查询/表格实现的目标,但也许这篇文章可能有所帮助:Using tables as Queues

<强>更新
如果NOLOCK读取将读取不一致状态(例如,读取陈旧的非聚集索引键并将其追逐到聚簇索引中的缺失行),则快照读取将在版本存储中找到“缺失”行。对于稳定数据,快照读取与nolock读取相同。每当数据发生变化(未提交更新)时,版本存储的神奇之处就会发挥作用,因为快照读取进入版本存储并找到“旧”值(稳定且一致),其中nolock读取将变得混乱并追逐指针lala land。

答案 1 :(得分:1)

在这种情况下你应该对NOLOCK安全。另外一个想法:在Ix索引上添加Value作为包含列应该消除对Cx的搜索。

create nonclustered index Ix on Data (Id, Date) include (Value)