Podane przez Ciebie gwarancje mają zastosowanie w tym prostym przypadku, ale niekoniecznie w nieco bardziej złożonych zapytaniach. Zobacz przykłady na końcu odpowiedzi.
Prosty przypadek
Zakładając, że col1 jest unikalny, ma dokładnie jedną wartość "2" lub ma stabilną kolejność, więc każda UPDATE
pasuje do tych samych wierszy w tej samej kolejności:
W przypadku tego zapytania wątki znajdą wiersz z kolumną col=2 i wszystkie spróbują złapać blokadę zapisu w tej krotce. Dokładnie jeden z nich odniesie sukces. Pozostałe zablokują oczekiwanie na zatwierdzenie transakcji pierwszego wątku.
Ten pierwszy tx zapisze, zatwierdzi i zwróci liczbę wierszy równą 1. Zatwierdzenie zwolni blokadę.
Inni nadawcy ponownie spróbują złapać zamek. Jeden po drugim osiągną sukces. Każda transakcja przejdzie z kolei przez następujący proces:
- Uzyskaj blokadę zapisu na kwestionowanej krotce.
- Sprawdź ponownie
WHERE col=2
stan po uzyskaniu blokady. - Ponowne sprawdzenie pokaże, że warunek już nie pasuje, więc
UPDATE
pominie ten wiersz. UPDATE
nie ma innych wierszy, więc zgłosi zaktualizowane zero wierszy.- Potwierdź, zwalniając blokadę dla następnej transmisji, próbując ją zdobyć.
W tym prostym przypadku blokada na poziomie wiersza i ponowne sprawdzenie warunku skutecznie serializują aktualizacje. W bardziej skomplikowanych przypadkach nie tak bardzo.
Możesz to łatwo zademonstrować. Otwórz powiedzmy cztery sesje psql. W pierwszym zablokuj tabelę za pomocą BEGIN; LOCK TABLE test;
. W pozostałych sesjach uruchom identyczną UPDATE
s - zablokują blokadę poziomu stołu. Teraz zwolnij blokadę przez COMMIT
pierwszą sesję. Zobacz, jak się ścigają. Tylko jeden zgłosi liczbę wierszy równą 1, pozostałe zgłoszą 0. Jest to łatwe do zautomatyzowania i oskryptowane do powtarzania i skalowania do większej liczby połączeń/wątków.
Aby dowiedzieć się więcej, przeczytaj zasady równoczesnego pisania , strona 11 Problemy ze współbieżnością PostgreSQL - a następnie przeczytaj resztę tej prezentacji.
A jeśli col1 nie jest unikalny?
Jak zauważył Kevin w komentarzach, jeśli col
nie jest unikalny, więc możesz dopasować wiele wierszy, a następnie różne wykonania UPDATE
może uzyskać różne zamówienia. Może się to zdarzyć, jeśli wybiorą różne plany (powiedzmy, że jeden jest przez PREPARE
i EXECUTE
a inny jest bezpośredni lub mieszasz z enable_
GUC) lub jeśli plan, z którego wszyscy korzystają, używa niestabilnego rodzaju równych wartości. Jeśli dostaną wiersze w innej kolejności, to tx1 zablokuje jedną krotkę, tx2 zablokuje inną, a następnie każdy z nich spróbuje uzyskać blokady na już zablokowanych krotkach. PostgreSQL przerwie jeden z nich z wyjątkiem zakleszczenia. To kolejny dobry powód, dla którego wszystkie Twój kod bazy danych powinien zawsze przygotuj się na ponowną próbę transakcji.
Jeśli uważasz, aby upewnić się, że jednoczesna UPDATE
Zawsze otrzymujesz te same wiersze w tej samej kolejności, nadal możesz polegać na zachowaniu opisanym w pierwszej części odpowiedzi.
Co frustrujące, PostgreSQL nie oferuje opcji UPDATE ... ORDER BY
więc upewnienie się, że twoje aktualizacje zawsze wybierają te same wiersze w tej samej kolejności, nie jest tak proste, jak możesz chcieć. SELECT ... FOR UPDATE ... ORDER BY
po którym następuje osobna UPDATE
jest często najbezpieczniejszy.
Bardziej złożone zapytania, systemy kolejkowe
Jeśli wykonujesz zapytania z wieloma fazami, obejmującymi wiele krotek lub warunki inne niż równość, możesz uzyskać zaskakujące wyniki, które różnią się od wyników wykonania seryjnego. W szczególności równoczesne uruchamianie czegoś takiego jak:
UPDATE test SET col = 1 WHERE col = (SELECT t.col FROM test t ORDER BY t.col LIMIT 1);
lub inne próby zbudowania prostego systemu „kolejkowego” będzie *nie działa* zgodnie z oczekiwaniami. Zobacz dokumentację PostgreSQL dotyczącą współbieżności i ta prezentacja aby uzyskać więcej informacji.
Jeśli chcesz, aby kolejka pracy była wspierana przez bazę danych, istnieją dobrze przetestowane rozwiązania, które radzą sobie ze wszystkimi zaskakująco skomplikowanymi przypadkami. Jednym z najpopularniejszych jest PgQ . Jest przydatny artykuł PgCon na ten temat i wyszukanie w Google „kolejki postgresql” jest pełen przydatnych wyników.
BTW, zamiast LOCK TABLE
możesz użyć SELECT 1 FROM test WHERE col = 2 FOR UPDATE;
aby uzyskać blokadę zapisu tylko na krotce. To zablokuje aktualizacje, ale nie zablokuje zapisów do innych krotek ani nie zablokuje żadnych odczytów. To pozwala symulować różne rodzaje problemów ze współbieżnością.