TLDR:尝试在包含另一个“父”表的外键的“子”表上按主键删除行时,它会在子项的持续时间内锁定父表交易。使用外键/子删除可以做些什么来防止锁定发生?
设定:
IF ( SELECT OBJECT_ID('dbo.Child')
) IS NOT NULL
DROP TABLE dbo.Child;
IF ( SELECT OBJECT_ID('dbo.Parent')
) IS NOT NULL
DROP TABLE dbo.Parent;
GO
CREATE TABLE dbo.Parent
(
ID INT PRIMARY KEY
IDENTITY(1, 1) ,
Value TINYINT NOT NULL
);
CREATE TABLE dbo.Child
(
ID INT PRIMARY KEY
IDENTITY(1, 1) ,
Parent_ID INT CONSTRAINT FK_Child_Parent_ID FOREIGN KEY REFERENCES Parent ( ID ) ,
Value TINYINT NOT NULL
);
GO
INSERT INTO dbo.Parent
( Value )
VALUES ( 1 ),
( 2 );
INSERT INTO dbo.Child
( Parent_ID, Value )
VALUES ( 1, 1 );
GO
连接1 :(先运行)
BEGIN TRANSACTION;
DELETE dbo.Child
WHERE Child.ID = 1;
连接2:
DELETE dbo.Parent
WHERE Parent.ID = 2;
在上面的场景中,连接2中的删除将被连接1阻止,直到该连接完成打开事务 - 即使父项上删除的行与被删除子项引用的行不同(并且实际上没有任何子条目。)
有没有办法修改约束以允许这种情况起作用?
答案 0 :(得分:5)
在这种情况下,您只需要在列Parent_ID上创建索引。它将强制查询优化器使用Index Seek操作
CREATE INDEX x ON dbo.Child(Parent_ID
)
否则Connection2将在表Child上进行Clustered Index Scan,它由Connection1阻塞
答案 1 :(得分:2)
问题是,您定义了一个约束,然后您希望sql server在删除行时忽略该约束。
目前因为子表正在引用父表,当您从Parent表中删除一行时,它会在Child表中查找任何可能的值,因为如果有任何外键约束,那么确保子表中没有留下孤立记录。由于任何列上都没有其他索引,因此需要查找聚簇索引。
如果查看两个语句的执行计划,子表的Delete语句是“扫描聚簇索引PK_Child__...
”。
现在,如果您查看Parent表中Delete语句的执行计划,它还会扫描子表中的Clustered Index PK_Child__...
。
由于您在第一个会话中有一个显式事务,它会在索引上获得一个独占锁,并阻止其他进程访问它,直到完成它为止。
正如Alex在另一篇文章中所建议的那样,如果您在引用回父表的列上的子表上创建了一个索引,则父表中的删除将具有该索引以进行查找,并且聚簇索引保持独立并且delete语句完成而没有任何阻塞。
CREATE INDEX IX_Child_Parent_ID
ON dbo.Child(Parent_ID)