我有一个示例情况:parent
表有一个名为 的列id
,在child
表中作为外键引用。
删除子行时,如果没有任何其他子行引用,如何删除父行?
我有一个示例情况:parent
表有一个名为 的列id
,在child
表中作为外键引用。
删除子行时,如果没有任何其他子行引用,如何删除父行?
在 PostgreSQL 9.1 或更高版本中,您可以通过使用数据修改 CTE的单个语句来执行此操作。这通常不太容易出错。它最小化了两个 DELETE 之间的时间范围,其中竞争条件可能导致并发操作的惊人结果:
WITH del_child AS (
DELETE FROM child
WHERE child_id = 1
RETURNING parent_id, child_id
)
DELETE FROM parent p
USING del_child x
WHERE p.parent_id = x.parent_id
AND NOT EXISTS (
SELECT 1
FROM child c
WHERE c.parent_id = x.parent_id
AND c.child_id <> x.child_id -- !
);
无论如何,孩子都会被删除。我引用手册:
中的数据修改语句
WITH
只执行一次,并且 总是完成,与主查询是否读取所有(或实际上任何)输出无关。SELECT
请注意,这与in的规则不同WITH
:如上一节所述,SELECT
仅在主查询需要其输出时才执行 a。
只有在没有其他子项时才会删除父项。
注意最后一个条件。与人们的预期相反,这是必要的,因为:
中的子语句彼此同时
WITH
执行并与主查询同时执行。因此,当在 中使用数据修改语句时,指定更新实际发生的顺序是不可预测的。所有语句都使用同一个快照执行(参见第 13 章),因此它们无法“看到”彼此对目标表的影响。WITH
大胆强调我的。
我用列名parent_id
代替了非描述性的id
.
为了完全消除我上面提到的可能的竞争条件,首先锁定父行。当然,所有类似的操作都必须遵循相同的程序才能使其工作。
WITH lock_parent AS (
SELECT p.parent_id, c.child_id
FROM child c
JOIN parent p ON p.parent_id = c.parent_id
WHERE c.child_id = 12 -- provide child_id here once
FOR NO KEY UPDATE -- locks parent row.
)
, del_child AS (
DELETE FROM child c
USING lock_parent l
WHERE c.child_id = l.child_id
)
DELETE FROM parent p
USING lock_parent l
WHERE p.parent_id = l.parent_id
AND NOT EXISTS (
SELECT 1
FROM child c
WHERE c.parent_id = l.parent_id
AND c.child_id <> l.child_id -- !
);
这样,一次只有一个事务可以锁定同一个父事务。所以不可能发生多个事务删除同一个父级的子级,仍然看到其他子级并保留父级,而所有子级都消失了。(仍然允许使用 . 更新非键列FOR NO KEY UPDATE
。)
如果这种情况永远不会发生,或者您可以忍受它(几乎永远不会)发生 - 第一个查询更便宜。否则,这是安全路径。
FOR NO KEY UPDATE
是在 Postgres 9.4 中引入的。手册中的详细信息。在旧版本中,请改用更强的锁FOR UPDATE
。
delete from child
where parent_id = 1
在子项中删除后,在父项中执行:
delete from parent
where
id = 1
and not exists (
select 1 from child where parent_id = 1
)
该not exists
条件将确保仅当子项中不存在它时才将其删除。您可以将两个删除命令包装在事务中:
begin;
first_delete;
second_delete;
commit;