我有一个示例情况:parent
表有一个名为 id
的列,在 child
表中作为外键引用。
删除子行时,如果没有其他子行引用,如何删除父行?
最佳答案
在 PostgreSQL 9.1 或更高版本 中,您可以通过使用 data-modifying 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 -- !
);
db<> fiddle here
<子>旧sqlfiddle
无论如何都会删除 child 。我引用 the manual :
Data-modifying statements in
WITH
are executed exactly once, and always to completion, independently of whether the primary query reads all (or indeed any) of their output. Notice that this is different from the rule forSELECT
inWITH
: as stated in the previous section, execution of aSELECT
is carried only as far as the primary query demands its output.
只有当父项没有其他个子项时才会被删除。
注意最后一个条件。与人们的预期相反,这是必要的,因为:
The sub-statements in
WITH
are executed concurrently with each other and with the main query. Therefore, when using data-modifying statements inWITH
, the order in which the specified updates actually happen is unpredictable. All the statements are executed with the same snapshot (see Chapter 13), so they cannot "see" each others' effects on the target tables.
大胆强调我的。
我使用列名 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 中引入的。 Details in the manual.在旧版本中,使用更强的锁 FOR UPDATE
代替。
关于sql - 如果其他子项未引用父项,则删除父项,我们在Stack Overflow上找到一个类似的问题: https://stackoverflow.com/questions/15809463/