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

Ben*_*nor 2 sql-server deadlock sql-server-2005 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)
Run Code Online (Sandbox Code Playgroud)

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

我有一个标准INSERT和SELECT之间的死锁情况,如下所示:

select top 1 Date, Value from Data where Id = @p0 order by Date desc
Run Code Online (Sandbox Code Playgroud)

因为INSERT获取关于CX(日期,标识;值)一个锁,然后IX(ID,日期),但SELECT获取关于IX(ID,日期),然后CX(日期,标识;值)的锁.这是因为SELECT首先寻找Ix然后加入到Cx上的搜索.

交换聚集和非聚集索引会打破这个循环中,但它不是可接受的解决方案,因为它会与其他(更复杂)选择引入周期.

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

  1. 不止一排,即使我要求TOP 1?
  2. 没有行,即使存在并且已经提交了?
  3. 最糟糕的是,一行不满足WHERE子句?

我已经在网上做了很多关于这个的阅读,但是我看到的(一个,两个)过度或不足的异常的唯一复制涉及扫描.这只涉及寻求.杰夫阿特伍德有一篇关于使用NOLOCK 的帖子,引起了很好的讨论.我对Rick Townsend的评论特别感兴趣:

其次,如果您读取脏数据,那么您运行的风险就是读取完全错误的行.例如,如果您的select读取索引以查找您的行,那么更新会更改行的位置(例如:由于页面拆分或对聚簇索引的更新),当您选择读取实际数据行时,它要么不再存在,要么完全不同!

这是否可以仅使用插入,并且没有更新?如果是这样,那么我想即使我在一个只插入表上的搜索也可能是危险的.


更新:

我试图找出快照隔离的工作原理.它似乎是基于行的,其中事务读取表(没有共享锁!),找到他们感兴趣的行,然后看看他们是否需要从tempdb中的版本存储中获取旧版本的行.

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

Rem*_*anu 7

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

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

不确定这是否适用,不清楚你试图用你的查询/表实现什么,但也许这篇文章可能有所帮助:使用表作为队列.

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