从 SQL 服务器中的多个表中删除多条记录的最佳做法

Best practices deleting many records from several tables in SQL Server

父 table 有数百万条记录,三个子 table 的外键指向父 table 的主键。像这样:

  Parent
 parent_id (PK) \       Child1
                |     child1_id (PK)
                |---- parent_id (FK)
                |
                |      Child2
                |     child2_id (PK)
                |---- parent_id (FK)
                |
                |      Child3
                |     child3_id (PK)
                |---- parent_id (FK)

Parent 中硬删除数十万条记录的最佳做法是什么?我想在以下条件下删除:DELETE FROM PARENT WHERE [STATUS] = 'DONE'。有没有办法在删除发生时不锁定 table(s)?以便其他记录可以插入到所有这些 table 中? 我能想到的选项:

  1. 在外键上使用 CASCADE DELETE
  2. 使用软删除:启动一个事务,UPDATE parent SET [DELETED] = 1 WHERE [STATUS] = 'DONE',删除每个具有这些父 ID 的子项,然后硬删除父项并提交。
  3. 与 2 类似,但使用过程并将要删除的那些 ID 保存在 table 变量中,这样我就不需要向 [=12] 添加新的 [DELETED] 列=] table.
  4. Select 要删除的 ID SELECT parent_id FROM parent WHERE [STATUS] = 'DONE' 然后通过所有这些 ID 进行批量删除。 (这表现得非常糟糕,所以我放弃了它)。

我正在使用 SQL Server 2014 和 spring jdbc。

我更喜欢用TOP x批量删除

所以对于每个 child table :

DELETE TOP 10000
FROM child1 
FROM child 1 as c1
INNER join parent
On parent_Id = c1.parent_id
AND parent.[STATUS] = 'DONE'

为每个 child table 重复多个批次。

您可以定期删除 parent 条没有 children 的记录。

DELETE TOP 10000
FROM parent 
FROM parent as p
Left outer join child1 c1
On p.parent_Id = c1.parent_id
AND c1.child_id IS NULL
 Left outer join child2 c2
On p.parent_Id = c2.parent_id
AND c2.child_id IS NULL
Left outer join child3 c3
On p.parent_Id = c3.parent_id
AND c3.child_id IS NULL
WHERE parent.[STATUS] = 'DONE'

每个 parent 有多少 children 将决定您 运行 和 parent 删除的频率。你当然可以改变 X 我会测试小,然后增加说 50000

Is there a way not to lock the table(s) while the delete is happening?

是的。正如您所建议的,批量操作而不是一次对数百万条记录进行操作将提高并发访问。

我从不使用级联删除,因为它是阴险的:它适用于大量的行,但对数百万行冷淡。而且我从不使用 TOP 任何东西,因为它不合逻辑:它使用任意数字而不是数据的某些方面。

每次我编写这样的程序时,我都会使用相同的技术。从底部开始,一个循环沿着主键删除数据的子集。当删除 returns 0 行受影响时,移动到下一个 table,依此类推,直到您可以删除最上面的行,不留下悬空引用。基本删除看起来像这样:

while @nrows > 0 begin
    delete from Child3
    where -- limitation criteria -- and
    parent_id = (
        select min(parent_id)
        from Parent
        where Status = 'DONE'
    )
    set @nrows = @@rowcount
done

如果您不能一次删除一个 parent_id 的所有行,出于性能原因,请找到一些限制子集,然后对其进行循环。也许是一个日期,一次删除一个月或一年。如果您可以一次删除多个父项,则一次选择其中的一个子集,并使用 exists 而不是最小值。

幸运的是,为此您不需要用户定义的事务。无论如何,这些行都是敬酒的,您可以在任何地方、任何时候重新开始,无论任何 'DONE' 父级是否仍然存在。