PostgreSQLの場合9.1以降 これは、データ変更CTEを使用して1つのステートメントで実行できます。 。これは一般的にエラーが発生しにくいです。 最小化 競合状態が発生する2つの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 -- !
);
SQLフィドル。
いずれの場合も、子は削除されます。マニュアルを引用します:
WITH
のデータ変更ステートメント 一度だけ実行され、常に完了する 、プライマリクエリが出力のすべて(または実際にいずれか)を読み取るかどうかに関係なく。これはSELECT
のルールとは異なることに注意してくださいWITH
:前のセクションで述べたように、SELECT
の実行 プライマリクエリがその出力を要求する場合にのみ実行されます。
親が削除されるのは、その他がない場合のみです。 子供。
最後の条件に注意してください。予想に反して、これは必要です。理由は次のとおりです。
WITH
のサブステートメント 同時に実行されます お互いにそしてメインクエリと。したがって、WITH
でデータ変更ステートメントを使用する場合 、指定された更新が実際に発生する順序は予測できません。すべてのステートメントは同じスナップショットで実行されるため(第13章を参照)、ターゲットテーブルに対する互いの影響を「見る」ことはできません。
大胆な強調鉱山。
列名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 -- !
);
この方法では、1つのみ 一度にトランザクションを実行すると、同じ親をロックできます。したがって、複数のトランザクションが同じ親の子を削除し、他の子を表示して親を惜しまない一方で、その後すべての子が削除されることはあり得ません。 (キー以外の列の更新は、FOR NO KEY UPDATE
でも引き続き許可されます。 。)
そのようなケースが発生しない場合、または発生する可能性がある場合(ほとんど発生しない場合)、最初のクエリの方が安価です。それ以外の場合、これは安全なパスです。
FOR NO KEY UPDATE
Postgres9.4で導入されました。マニュアルの詳細。古いバージョンでは、より強力なロックFOR UPDATE
を使用します 代わりに。