V PostgreSQL 9.1 nebo novější můžete to udělat jediným příkazem pomocí CTE modifikujícího data . To je obecně méně náchylné k chybám. Minimalizuje časový rámec mezi dvěma DELETE, ve kterém podmínky závodu může vést k překvapivým výsledkům při souběžných operacích:
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 Fiddle.
Dítě je v každém případě smazáno. Cituji manuál:
Příkazy upravující data v
WITH
jsou provedeny přesně jednou avždy do dokončení nezávisle na tom, zda primární dotaz přečte všechny (nebo skutečně jakýkoli) jejich výstup. Všimněte si, že se to liší od pravidla proSELECT
vWITH
:jak je uvedeno v předchozí části, provedeníSELECT
je přenášen pouze tak daleko, jak primární dotaz vyžaduje jeho výstup.
Rodič je smazán pouze v případě, že nemá žádné jiné děti.
Všimněte si poslední podmínky. Na rozdíl od toho, co by se dalo očekávat, je to nutné, protože:
Dílčí příkazy v
WITH
jsou prováděny současně mezi sebou as hlavním dotazem. Proto při použití příkazů modifikujících data vWITH
, pořadí, ve kterém se zadané aktualizace skutečně stanou, je nepředvídatelné. Všechny příkazy jsou prováděny se stejným snímkem (viz kapitola 13), takže nemohou „vidět“ vzájemné účinky na cílové tabulky.
Tučné zdůraznění min.
Použil jsem název sloupce parent_id
místo nepopisného id
.
Eliminace race condition
Abych eliminoval možné podmínky závodu, zcela jsem zmínil výše , nejprve uzamkněte nadřazený řádek . Samozřejmě, vše aby podobné operace fungovaly, musí se řídit stejným postupem.
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 -- !
);
Tímto způsobem pouze jeden transakce najednou může uzamknout stejného rodiče. Nemůže se tedy stát, že více transakcí odstraní děti stejného rodiče, stále uvidí další děti a ušetří rodiče, zatímco všechny děti budou poté pryč. (Aktualizace neklíčových sloupců jsou stále povoleny pomocí FOR NO KEY UPDATE
.)
Pokud se takové případy nikdy nevyskytnou nebo se s tím (málokdy) dá žít - první dotaz je levnější. Jinak toto je bezpečná cesta.
FOR NO KEY UPDATE
byl představen s Postgres 9.4. Podrobnosti v návodu. Ve starších verzích použijte silnější zámek FOR UPDATE
místo toho.