在我们的一个数据库中,我们有一个表,该表被多个线程密集并发访问。线程确实通过来更新或插入行MERGE
。还有一些线程有时会删除行,因此表数据非常不稳定。进行upsert的线程有时会陷入死锁。该问题看起来类似于此问题中描述的问题。不过,不同之处在于,在我们的例子中,每个线程确实更新或插入一行。
简化的设置如下。该表是堆,上面有两个唯一的非聚集索引
CREATE TABLE [Cache]
(
[UID] uniqueidentifier NOT NULL CONSTRAINT DF_Cache_UID DEFAULT (newid()),
[ItemKey] varchar(200) NOT NULL,
[FileName] nvarchar(255) NOT NULL,
[Expires] datetime2(2) NOT NULL,
CONSTRAINT [PK_Cache] PRIMARY KEY NONCLUSTERED ([UID])
)
GO
CREATE UNIQUE INDEX IX_Cache ON [Cache] ([ItemKey]);
GO
典型的查询是
DECLARE
@itemKey varchar(200) = 'Item_0F3C43A6A6A14255B2EA977EA730EDF2',
@fileName nvarchar(255) = 'File_0F3C43A6A6A14255B2EA977EA730EDF2.dat';
MERGE INTO [Cache] WITH (HOLDLOCK) T
USING (
VALUES (@itemKey, @fileName, dateadd(minute, 10, sysdatetime()))
) S(ItemKey, FileName, Expires)
ON T.ItemKey = S.ItemKey
WHEN MATCHED THEN
UPDATE
SET
T.FileName = S.FileName,
T.Expires = S.Expires
WHEN NOT MATCHED THEN
INSERT (ItemKey, FileName, Expires)
VALUES (S.ItemKey, S.FileName, S.Expires)
OUTPUT deleted.FileName;
即,匹配通过唯一的索引键发生。提示HOLDLOCK
是在这里,因为并发(如建议在这里)。
我做了一些小调查,以下是我发现的内容。
在大多数情况下,查询执行计划是
具有以下锁定模式
即IX
锁定对象,然后再进行更细化的锁定。
但是,有时查询执行计划是不同的
(可以通过添加INDEX(0)
提示来强制此计划形状),并且其锁定模式为
通知X
锁IX
已经放置在对象上。
由于两个IX
是兼容的,但两个X
不兼容,因此在并发下发生的事情是
僵局!
问题的第一部分出现了。是否X
在IX
符合条件的情况下锁定对象?不是虫子吗?
文档状态:
意向锁之所以称为意向锁,是因为它们是在较低级别的锁之前获取的,因此发出意向将锁置于较低级别的意图。
并且还
IX表示仅更新部分行而不是全部行的意图
因此,在我看来非常可疑X
之后,将物体锁定在物体上IX
。
首先,我尝试通过尝试添加表锁定提示来防止死锁
MERGE INTO [Cache] WITH (HOLDLOCK, TABLOCK) T
和
MERGE INTO [Cache] WITH (HOLDLOCK, TABLOCKX) T
与TABLOCK
到位锁定模式变
与TABLOCKX
锁定模式是
由于两个SIX
(以及两个X
)不兼容,因此可以有效防止死锁,但是不幸的是,还可以防止并发(这是不希望的)。
我的下一个尝试是在加入PAGLOCK
和ROWLOCK
使锁具更细化和减少争。两者均无效(X
此后仍可观察到物体IX
)。
我最后的尝试是通过添加FORCESEEK
提示来强制执行具有良好粒度锁定的“良好”执行计划
MERGE INTO [Cache] WITH (HOLDLOCK, FORCESEEK(IX_Cache(ItemKey))) T
而且有效。
问题的第二部分出现了。难道FORCESEEK
会被忽略并使用错误的锁定模式吗?(正如我提到的,PAGLOCK
并且ROWLOCK
似乎被忽略了)。
添加UPDLOCK
无效(X
对之后仍然可见的对象IX
)。
IX_Cache
正如预期的那样,使索引聚类。它导致了使用聚集索引查找和粒度锁定的计划。另外,我尝试强制显示聚类索引的“聚集索引扫描”。
然而。补充观察。即使在原始设置FORCESEEK(IX_Cache(ItemKey)))
中,如果将@itemKey
变量声明从varchar(200)更改为nvarchar(200),执行计划也将变为
看到使用了搜索,但是在这种情况下,BUT锁定模式再次X
在后面显示了对对象的锁定IX
。
因此,似乎强制寻找并不一定保证粒度锁定(因此也没有死锁)。我不确定具有聚集索引可以保证粒度锁定。还是呢?
我的理解(如果我错了,请纠正我)是锁定在很大程度上是情景的,并且特定的执行计划形状并不意味着特定的锁定模式。
关于仍然打开X
后在对象上放置锁定的资格的问题IX
。如果符合条件,是否可以做一些事情来防止对象锁定?