Mam bazę danych Postgres, która zawiera szczegółowe informacje o klastrach serwerów, takie jak status serwera („aktywny”, „tryb gotowości” itp.). Aktywne serwery w dowolnym momencie mogą wymagać przełączenia awaryjnego do trybu gotowości i nie dbam o to, który tryb gotowości jest używany w szczególności.
Chcę, aby zapytanie do bazy danych zmieniło stan gotowości - JUST ONE - i zwróci adres IP serwera, który będzie używany. Wybór może być dowolny: ponieważ status serwera zmienia się wraz z zapytaniem, nie ma znaczenia, który tryb gotowości zostanie wybrany.
Czy mogę ograniczyć moje zapytanie do jednej aktualizacji?
Oto co mam do tej pory:
UPDATE server_info SET status = 'active'
WHERE status = 'standby' [[LIMIT 1???]]
RETURNING server_ip;
Postgres tego nie lubi. Co mogę zrobić inaczej?
postgresql
update
concurrency
queue
ogromnie superorman
źródło
źródło
Odpowiedzi:
Bez równoczesnego dostępu do zapisu
Zmaterializuj zaznaczenie w CTE i dołącz do niego w
FROM
klauzuliUPDATE
.Pierwotnie miałem tutaj proste podzapytanie, ale może to pomijać
LIMIT
niektóre plany zapytań, jak zauważył Feike :Lub użyj słabo skorelowanego podzapytania dla prostego przypadku z
LIMIT
1
. Prostsze, szybsze:Z jednoczesnym dostępem do zapisu
Zakładając domyślny poziom izolacji
READ COMMITTED
dla tego wszystkiego. Zaostrzone poziomy izolacji (REPEATABLE READ
iSERIALIZABLE
) mogą nadal powodować błędy serializacji. Widzieć:Przy jednoczesnym obciążeniu zapisu dodaj,
FOR UPDATE SKIP LOCKED
aby zablokować wiersz, aby uniknąć warunków wyścigu.SKIP LOCKED
został dodany w Postgres 9.5 , dla starszych wersji patrz poniżej. Instrukcja:Jeśli nie ma kwalifikującego się, odblokowanego wiersza, w tym zapytaniu nic się nie dzieje (żaden wiersz nie jest aktualizowany), a wynik jest pusty. W przypadku bezkrytycznych operacji oznacza to, że skończyłeś.
Jednak współbieżne transakcje mogą mieć zablokowane wiersze, ale nie kończą aktualizacji (
ROLLBACK
lub z innych powodów). Aby mieć pewność, wykonaj ostatnią kontrolę:SELECT
widzi również zablokowane rzędy. Wile, które nie zwracatrue
, jeden lub więcej wierszy jest nadal przetwarzanych, a transakcje można nadal wycofywać. (W międzyczasie dodano nowe wiersze.) Poczekaj chwilę, a następnie zapętl dwa kroki: (UPDATE
dopóki nie odzyskasz żadnego wiersza;SELECT
...), aż dostaniesztrue
.Związane z:
Bez
SKIP LOCKED
w PostgreSQL 9.4 lub starszymRównoczesne transakcje próbujące zablokować ten sam wiersz są blokowane, dopóki pierwszy nie zwolni blokady.
Jeśli pierwszy został wycofany, następna transakcja bierze blokadę i przebiega normalnie; inni w kolejce czekają.
Jeśli pierwszy zatwierdzony,
WHERE
warunek jest ponownie oceniany, a jeśli już nieTRUE
jest (status
zmienił się), CTE (nieco zaskakująco) nie zwraca wiersza. Nic się nie dzieje. Jest to pożądane zachowanie, gdy wszystkie transakcje chcą zaktualizować ten sam wiersz .Ale nie przy każdej transakcji chce zaktualizować do następnego wiersza . A ponieważ chcemy po prostu zaktualizować dowolny (lub losowy ) wiersz , nie ma sensu w ogóle czekać.
Możemy odblokować sytuację za pomocą blokad doradczych :
W ten sposób następny niezablokowany jeszcze wiersz zostanie zaktualizowany. Każda transakcja otrzymuje nowy wiersz do pracy. Miałem pomoc z Czech Postgres Wiki w tej sztuczce.
id
będący dowolną unikalnąbigint
kolumną (lub dowolnym typem z niejawną obsadą, taką jakint4
lubint2
).Jeśli blokady doradcze są używane jednocześnie dla wielu tabel w bazie danych, należy jednoznacznie ustalić, że
pg_try_advisory_xact_lock(tableoid::int, id)
-id
jestinteger
tutaj wyjątkowy .Ponieważ
tableoid
jest tobigint
ilość, teoretycznie może się przepełnićinteger
. Jeśli jesteś wystarczająco paranoikiem, użyj(tableoid::bigint % 2147483648)::int
zamiast tego - pozostawiając teoretyczną „kolizję skrótu” dla prawdziwie paranoicznej ...Ponadto Postgres może testować
WHERE
warunki w dowolnej kolejności. To mogło testowaćpg_try_advisory_xact_lock()
i uzyskać blokadę przedstatus = 'standby'
, co może wiązać się z dodatkowymi zamkami doradczych na niepowiązanych wierszy, w którychstatus = 'standby'
nie jest to prawdą. Powiązane pytanie dotyczące SO:Zazwyczaj możesz to zignorować. Aby zagwarantować, że tylko kwalifikujące się wiersze są zablokowane, możesz zagnieździć predykat (y) w CTE jak wyżej lub podkwerendę z
OFFSET 0
hackem (zapobiega wstawianiu) . Przykład:Lub (tańsze w przypadku skanowania sekwencyjnego) zagnieżdżają warunki w
CASE
instrukcji, takiej jak:Jednak
CASE
Sztuką byłoby również zachować Postgresa z użyciem indeksustatus
. Jeśli taki indeks jest dostępny, na początku nie potrzebujesz dodatkowego zagnieżdżania: tylko skanowane wiersze zostaną zablokowane.Ponieważ nie możesz mieć pewności, że indeks jest używany przy każdym połączeniu, możesz po prostu:
CASE
Jest logicznie zbędny, ale serwery omawiany cel.Jeśli polecenie jest częścią długiej transakcji, rozważ blokady na poziomie sesji, które można (i trzeba) zwolnić ręcznie. Możesz więc odblokować, jak tylko skończysz z zablokowanym rzędem:
pg_try_advisory_lock()
ipg_advisory_unlock()
. Instrukcja:Związane z:
źródło