2

我知道快照隔离可以解决这个问题,但我想知道 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 (Date, Id; Value) 上获得锁,然后在 Ix (Id, Date) 上获得锁,但 SELECT 在 Ix (Id, Date) 上获得锁,然后在 Cx (Date, Id; Value) 上获得锁。这是因为 SELECT 首先在 Ix 上查找,然后加入到 Cx 上的查找。

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

如果我将 NOLOCK 添加到 SELECT,在这种情况下会出错吗?能否返回:

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

我已经在网上阅读了很多关于此的内容,但我所见过的计数过多或过少异常的唯一复制品()涉及扫描。这仅涉及搜索。Jeff Atwood有一篇关于使用 NOLOCK 的帖子引起了很好的讨论。我对 Rick Townsend 的评论特别感兴趣:

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

这是否可能仅使用插入而不进行更新?如果是这样,那么我想即使我在仅插入表上的搜索也可能很危险。


更新:

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

但在我的情况下,没有一行会有多个版本,所以版本存储似乎毫无意义。如果发现该行没有共享锁,与只使用 NOLOCK 有何不同?

4

2 回答 2

7

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

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

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

更新
在 NOLOCK 读取将读取不一致状态的情况下(例如,读取过时的非聚集索引键并将其追踪到聚集索引中的缺失行),快照读取将在版本存储中找到“缺失”行。对于稳定数据,快照读取与非锁定读取相同。每当数据更改(未提交的更新)时,版本存储的魔力就会发挥作用,因为快照读取进入版本存储并找到“旧”值(稳定且一致),其中 nolock 读取将变得混乱并追逐指针啦啦土地。

于 2010-06-08T22:14:41.370 回答
1

在这种情况下,您应该使用 NOLOCK 是安全的。另一个想法:将值添加为 Ix 索引上的包含列应该消除对 Cx 的查找。

create nonclustered index Ix on Data (Id, Date) include (Value)
于 2010-06-08T21:56:30.950 回答