SQL блокирует родительскую таблицу при удалении строки дочерней таблицы

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 ответов


в этом сценарии вам просто нужно создать индекс в столбце Parent_ID. Это заставит оптимизатор запросов использовать операцию поиска индекса

CREATE INDEX x ON dbo.Child(Parent_ID)

enter image description here

в противном случае Connection2 будет иметь Кластеризованное сканирование индекса на дочерней таблице, которая блокирует Connection1

enter image description here


проблема в том, что вы определяете ограничение, а затем ожидаете, что sql server проигнорирует это ограничение, когда дело доходит до удаления строки.

в данный момент, поскольку родительская таблица ссылается на дочернюю таблицу, при удалении строки из родительской таблицы она ищет любые возможные значения в дочерней таблице, потому что если есть какие-либо ограничения внешнего ключа, чтобы убедиться, что в дочерней таблице не осталось сиротских записей. Поскольку других индексов нет любой из столбцов должен искать кластеризованный индекс.

Если вы посмотрите на планы выполнения для обоих операторов, оператор Delete для дочерней таблицы сканирует кластеризованный индекс PK_Child__....

enter image description here

Теперь, если вы посмотрите на план выполнения инструкции Delete в родительской таблице, он также сканирует кластеризованный индекс PK_Child__... в дочерней таблице.

enter image description here

Так как вы имейте явную транзакцию в первом сеансе, она получает эксклюзивную блокировку индекса и останавливает другие процессы для доступа к нему, пока не закончит с ним.

как предложил Алекс в другом сообщении, Если вы создали индекс в дочерней таблице в столбце, ссылающемся на родительскую таблицу, то удаление в родительской таблице будет иметь этот индекс для поиска, а кластеризованный индекс останется один, и оператор delete завершится без какой-либо блокировки.

CREATE INDEX IX_Child_Parent_ID 
ON dbo.Child(Parent_ID)

enter image description here