2

从几天开始,我正在考虑以下情况

考虑我有 2 个表,其父子关系是一对多的。在删除父行时,我必须删除子行中与父母相关的行。简单吧?

我必须创建一个事务范围来执行上述操作我可以按以下方式执行此操作;(它的伪代码,但我使用 odbc 连接在 c# 代码中执行此操作,数据库是 sql server)

  1. 开始事务(读取已提交)
  2. 读取 child.fk = p1 的所有孩子
  3. foreach(child) 删除 child where child.pk = cx
  4. 删除 parent.pk = p1 的父级
  5. 提交反式

或者

  1. 开始事务(读取已提交)
  2. 删除 child.fk = p1 的所有孩子
  3. 删除 parent.pk = p1 的父级
  4. 提交反式

现在我有几个问题

  1. 考虑到在几秒钟内执行数千个其他操作(选择/更新/删除/插入)的实时系统场景,以上哪一个更好。

  2. 它是否确保在事务完成之前不会添加具有 child.fk = p1 的新孩子?

  3. 如果第二个问题是,那么它如何确保?它需要表级锁还是什么。

  4. sql server 是否支持任何类型的索引锁定,如果是,它的作用以及如何使用它。

问候穆巴沙尔

4

2 回答 2

2

我从不使用(也从未有过合法需要)级联删除,也没有使用触发器来强制执行此操作。造成这种情况的主要原因通常是:

  • 应用程序中甚至不允许删除 - 事物被标记为已删除,或者它们在所有时间都是时间一致的,并且具有生效日期、终止日期等。
  • 我想知道是否意外删除了父级,与它们关联的所有内容都不会简单地消失-因此,没有级联删除的 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
于 2010-04-04T11:43:29.403 回答
0

你的两种方法都是错误的。您应该始终:

  • 先插入父母,再插入孩子
  • 先更新父,再更新子
  • 先删除父项,再删除子项
  • 先选父母,再选孩子

更好的是,使用级联删除声明参照完整性,并让其处理子项的删除。

问题的要点是您必须选择一个订单并坚持下去,无论是父母对孩子还是孩子对父母。后者在大多数情况下没有意义,所以最好坚持第一个。

于 2010-04-04T07:22:44.907 回答