W PostgreSQL 9.1 lub nowszym możesz to zrobić za pomocą jednej instrukcji, używając modyfikacji danych CTE . Jest to generalnie mniej podatne na błędy. minimalizuje przedział czasowy między dwoma DELETE, w których warunki wyścigu może prowadzić do zaskakujących wyników przy równoczesnych operacjach:
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 -- !
);
Skrzypce SQL.
W każdym przypadku dziecko jest usuwane. Cytuję instrukcję:
Instrukcje modyfikujące dane w WITH
są wykonywane dokładnie raz izawsze do końca , niezależnie od tego, czy zapytanie podstawowe odczytuje wszystkie (lub w rzeczywistości dowolne) ich dane wyjściowe. Zauważ, że różni się to od reguły SELECT
w WITH
:jak podano w poprzedniej sekcji, wykonanie SELECT
jest przenoszony tylko tak daleko, jak podstawowe zapytanie wymaga jego wyjścia.
Rodzic jest usuwany tylko wtedy, gdy nie ma innych dzieci.
Zwróć uwagę na ostatni warunek. Wbrew pozorom jest to konieczne, ponieważ:
Podzgłoszenia w WITH
są wykonywane równocześnie ze sobą i z głównym zapytaniem. Dlatego podczas używania instrukcji modyfikujących dane w WITH
, kolejność, w jakiej faktycznie występują określone aktualizacje, jest nieprzewidywalna. Wszystkie instrukcje są wykonywane z tą samą migawką (patrz rozdział 13), więc nie mogą "zobaczyć" innych efektów na tabelach docelowych.
Pogrubiony nacisk na kopalnię.
Użyłem nazwy kolumny parent_id
w miejsce nieopisowego id
.
Wyeliminuj sytuację wyścigową
Aby wyeliminować możliwe warunki wyścigu, wspomniałem powyżej całkowicie , zablokuj wiersz nadrzędny pierwszy . Oczywiście wszystkie podobne operacje muszą przebiegać według tej samej procedury, aby działały.
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 -- !
);
W ten sposób tylko jeden Transakcja na raz może zablokować tego samego rodzica. Nie może więc zdarzyć się, że wielokrotne transakcje usuną dzieci tego samego rodzica, nadal zobaczą inne dzieci i oszczędzą rodzica, podczas gdy wszystkie dzieci znikną później. (Aktualizacje w kolumnach bez klucza są nadal dozwolone za pomocą FOR NO KEY UPDATE
.)
Jeśli takie przypadki nigdy się nie zdarzają lub można z tym żyć (prawie nigdy) - pierwsze zapytanie jest tańsze. W przeciwnym razie jest to bezpieczna ścieżka.
FOR NO KEY UPDATE
został wprowadzony wraz z Postgresem 9.4. Szczegóły w instrukcji. W starszych wersjach użyj mocniejszego zamka FOR UPDATE
zamiast tego.