SQL在删除子表行时锁定父表

时间:2015-10-15 20:47:35

标签: sql-server tsql

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阻止,直到该连接完成打开事务 - 即使父项上删除的行与被删除子项引用的行不同(并且实际上没有任何子条目。)

有没有办法修改约束以允许这种情况起作用?

2 个答案:

答案 0 :(得分:5)

在这种情况下,您只需要在列Parent_ID上创建索引。它将强制查询优化器使用Index Seek操作

CREATE INDEX x ON dbo.Child(Parent_ID

enter image description here

否则Connection2将在表Child上进行Clustered Index Scan,它由Connection1阻塞

enter image description here

答案 1 :(得分:2)

问题是,您定义了一个约束,然后您希望sql server在删除行时忽略该约束。

目前因为子表正在引用父表,当您从Parent表中删除一行时,它会在Child表中查找任何可能的值,因为如果有任何外键约束,那么确保子表中没有留下孤立记录。由于任何列上都没有其他索引,因此需要查找聚簇索引。

如果查看两个语句的执行计划,子表的Delete语句是“扫描聚簇索引PK_Child__...”。

enter image description here

现在,如果您查看Parent表中Delete语句的执行计划,它还会扫描子表中的Clustered Index PK_Child__...

enter image description here

由于您在第一个会话中有一个显式事务,它会在索引上获得一个独占锁,并阻止其他进程访问它,直到完成它为止。

正如Alex在另一篇文章中所建议的那样,如果您在引用回父表的列上的子表上创建了一个索引,则父表中的删除将具有该索引以进行查找,并且聚簇索引保持独立并且delete语句完成而没有任何阻塞。

CREATE INDEX IX_Child_Parent_ID 
ON dbo.Child(Parent_ID)

enter image description here