我知道快照隔离可以解决这个问题,但我想知道 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,在这种情况下会出错吗?能否返回:
- 不止一排,即使我要求 TOP 1?
- 没有行,即使存在并已提交?
- 最糟糕的是,一行不满足 WHERE 子句?
我已经在网上阅读了很多关于此的内容,但我所见过的计数过多或过少异常的唯一复制品(一、二)涉及扫描。这仅涉及搜索。Jeff Atwood有一篇关于使用 NOLOCK 的帖子引起了很好的讨论。我对 Rick Townsend 的评论特别感兴趣:
其次,如果您读取脏数据,您面临的风险是读取完全错误的行。例如,如果您的选择读取索引以查找您的行,那么当您的选择读取实际数据行时,更新会更改行的位置(例如:由于页面拆分或对聚集索引的更新) ,它要么不再存在,要么完全不同!
这是否可能仅使用插入而不进行更新?如果是这样,那么我想即使我在仅插入表上的搜索也可能很危险。
更新:
我试图弄清楚快照隔离是如何工作的。它似乎是基于行的,其中事务读取表(没有共享锁!),找到他们感兴趣的行,然后查看他们是否需要从 tempdb 中的版本存储中获取该行的旧版本。
但在我的情况下,没有一行会有多个版本,所以版本存储似乎毫无意义。如果发现该行没有共享锁,与只使用 NOLOCK 有何不同?