Я никогда не использую (и никогда не имел законной необходимости) каскадное удаление, а также не использовал триггеры для обеспечения этого. Основными причинами этого являются:
- Удаление даже не разрешено в приложении - вещи помечены как удаленные, или они постоянно согласованы во времени и имеют даты вступления в силу, даты прекращения и т. Д.
- Я хочу знать, если родитель случайно удален, что все связанное с ним содержимое не просто исчезает - поэтому RI без каскадного удаления защищает от удаления целое дерево зависимостей
- Заставляет проект приложения и базы данных быть более внимательным к взаимозависимостям сущностей и обеспечивать надлежащий рефакторинг как структуры, так и процессов.
- Принудительное создание соответствующей процедуры удаления для сущности позволяет вам выбирать порядок каждого шага и потенциально избегать взаимных блокировок, а также обеспечивать настройку ваших запросов.
Единственное преимущество каскадного удаления, которое я вижу, состоит в том, что оно декларативное, определено в таблицах и, по-видимому, будет иметь наименьший возможный объем расширения блокировки. Вышеуказанные преимущества перевешивают его использование, на мой взгляд.
Как и во втором примере, я бы включил в транзакцию (обычно в хранимой процедуре):
DELETE FROM child WHERE child.fk IN (set to delete);
DELETE FROM parent WHERE parent.pk IN (set to delete);
Вся транзакция либо удастся оставить вашу базу данных в согласованном состоянии, либо не сможет зафиксировать какие-либо изменения, если по какой-либо причине не удастся удалить все дочерние элементы или родительский элемент, т. Е. Если была другая ссылка FK на дочернего элемента или родительского элемента, не учитываемого в вашем удалении.
База данных всегда будет обеспечивать вашу ссылочную целостность.
USE SandBox
GO
IF EXISTS ( SELECT *
FROM sys.objects
WHERE object_id = OBJECT_ID(N'Child')
AND type IN ( N'U' ) )
DROP TABLE dbo.Child
GO
IF EXISTS ( SELECT *
FROM sys.objects
WHERE object_id = OBJECT_ID(N'Parent')
AND type IN ( N'U' ) )
DROP TABLE dbo.Parent
GO
CREATE TABLE Parent
(
PK INT NOT NULL
IDENTITY
,Nm VARCHAR(15)
,PRIMARY KEY ( PK )
)
GO
CREATE TABLE Child
(
PK INT NOT NULL
IDENTITY
,FK INT NOT NULL
,Nm VARCHAR(15)
,PRIMARY KEY ( PK )
)
GO
ALTER TABLE Child
WITH CHECK
ADD CONSTRAINT FK_Child_Parent FOREIGN KEY ( FK ) REFERENCES Parent ( PK )
GO
DECLARE @LastParent AS INT
INSERT INTO Parent ( Nm )
VALUES ( 'Donald Duck' )
SET @LastParent = SCOPE_IDENTITY()
INSERT INTO Child ( FK, Nm )
VALUES ( @LastParent, 'Huey' )
INSERT INTO Child ( FK, Nm )
VALUES ( @LastParent, 'Dewey' )
INSERT INTO Child ( FK, Nm )
VALUES ( @LastParent, 'Louie' )
SELECT *
FROM Parent
SELECT *
FROM Child
GO
BEGIN TRANSACTION
DELETE FROM Child
WHERE FK = ( SELECT PK
FROM Parent
WHERE Nm = 'Donald Duck'
)
-- Run just to here
-- In another session do this:
-- INSERT INTO Child (FK, Nm) VALUES ((SELECT PK FROM Parent WHERE Nm = 'Donald Duck'), 'Cuckoo')
-- Then return here
DELETE FROM Parent
WHERE Nm = 'Donald Duck' -- Should fail
IF @@ERROR <> 0
ROLLBACK TRANSACTION
ELSE
COMMIT TRANSACTION
SELECT *
FROM Parent
SELECT *
FROM Child