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
)
в противном случае Connection2 будет иметь Кластеризованное сканирование индекса на дочерней таблице, которая блокирует Connection1
проблема в том, что вы определяете ограничение, а затем ожидаете, что sql server проигнорирует это ограничение, когда дело доходит до удаления строки.
в данный момент, поскольку родительская таблица ссылается на дочернюю таблицу, при удалении строки из родительской таблицы она ищет любые возможные значения в дочерней таблице, потому что если есть какие-либо ограничения внешнего ключа, чтобы убедиться, что в дочерней таблице не осталось сиротских записей. Поскольку других индексов нет любой из столбцов должен искать кластеризованный индекс.
Если вы посмотрите на планы выполнения для обоих операторов, оператор Delete для дочерней таблицы сканирует кластеризованный индекс PK_Child__...
.
Теперь, если вы посмотрите на план выполнения инструкции Delete в родительской таблице, он также сканирует кластеризованный индекс PK_Child__...
в дочерней таблице.
Так как вы имейте явную транзакцию в первом сеансе, она получает эксклюзивную блокировку индекса и останавливает другие процессы для доступа к нему, пока не закончит с ним.
как предложил Алекс в другом сообщении, Если вы создали индекс в дочерней таблице в столбце, ссылающемся на родительскую таблицу, то удаление в родительской таблице будет иметь этот индекс для поиска, а кластеризованный индекс останется один, и оператор delete завершится без какой-либо блокировки.
CREATE INDEX IX_Child_Parent_ID
ON dbo.Child(Parent_ID)