Twoje opcje to:
-
Uruchom w
SERIALIZABLE
izolacja. Transakcje współzależne zostaną przerwane po zatwierdzeniu, ponieważ wystąpiły niepowodzenie serializacji. Otrzymasz dużo spamu w dziennikach błędów i będziesz wykonywać wiele ponownych prób, ale będzie działać niezawodnie. -
Zdefiniuj
UNIQUE
ograniczenie i ponowna próba w przypadku niepowodzenia, jak zauważyłeś. Takie same problemy jak powyżej. -
Jeśli istnieje obiekt nadrzędny, możesz
SELECT ... FOR UPDATE
obiekt nadrzędny przed wykonaniemmax
zapytanie. W tym przypadkuSELECT 1 FROM bar WHERE bar_id = $1 FOR UPDATE
. Używaszbar
jako blokada dla wszystkichfoo
s z tymbar_id
. Możesz wtedy wiedzieć, że kontynuowanie jest bezpieczne, o ile każde zapytanie, które wykonuje przyrost licznika, robi to niezawodnie. To może działać całkiem dobrze.To nadal tworzy agregujące zapytanie dla każdego wywołania, które (na następną opcję) jest niepotrzebne, ale przynajmniej nie spamuje dziennika błędów, jak powyższe opcje.
-
Użyj stolika licznikowego. To właśnie bym zrobił. Albo w
bar
lub w tabeli bocznej, takiej jakbar_foo_counter
, uzyskaj identyfikator wiersza za pomocąUPDATE bar_foo_counter SET counter = counter + 1 WHERE bar_id = $1 RETURNING counter
lub mniej wydajna opcja, jeśli Twój framework nie obsługuje
RETURNING
:SELECT counter FROM bar_foo_counter WHERE bar_id = $1 FOR UPDATE; UPDATE bar_foo_counter SET counter = $1;
Następnie w tej samej transakcji , użyj wygenerowanego wiersza licznika jako
number
. Po zatwierdzeniu wiersz tabeli liczników dla tegobar_id
zostanie odblokowany do użycia w następnym zapytaniu. Jeśli cofniesz, zmiana zostanie odrzucona.
Polecam podejście licznikowe, używając dedykowanej tabeli bocznej dla licznika zamiast dodawania kolumny do bar
. Model jest czystszy i oznacza, że w bar
tworzysz mniej wzdęć aktualizacji , co może spowolnić zapytania do bar
.