我从不使用(也从未有过合法需要)级联删除,也没有使用触发器来强制执行此操作。造成这种情况的主要原因通常是:
- 应用程序中甚至不允许删除 - 事物被标记为已删除,或者它们在所有时间都是时间一致的,并且具有生效日期、终止日期等。
- 我想知道是否意外删除了父级,与它们关联的所有内容都不会简单地消失-因此,没有级联删除的 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