Muszę zachować unikalny (w wierszu) numer rewizji w tabeli rewizji dokumentu, gdzie numer rewizji jest zawarty w dokumencie, więc nie jest unikalny dla całej tabeli, tylko dla powiązanego dokumentu.
Początkowo wymyśliłem coś takiego:
current_rev = SELECT MAX(rev) FROM document_revisions WHERE document_id = 123;
INSERT INTO document_revisions(rev) VALUES(current_rev + 1);
Ale jest warunek wyścigu!
Próbuję to rozwiązać pg_advisory_lock
, ale dokumentacja jest nieco skąpa i nie do końca ją rozumiem, i nie chcę przez pomyłkę czegoś blokować.
Czy poniższe są dopuszczalne, czy robię to źle, czy jest lepsze rozwiązanie?
SELECT pg_advisory_lock(123);
current_rev = SELECT MAX(rev) FROM document_revisions WHERE document_id = 123;
INSERT INTO document_revisions(rev) VALUES(current_rev + 1);
SELECT pg_advisory_unlock(123);
Czy zamiast tego nie powinienem blokować wiersza dokumentu (klucz1) dla danej operacji (klucz2)? To byłoby właściwe rozwiązanie:
SELECT pg_advisory_lock(id, 1) FROM documents WHERE id = 123;
current_rev = SELECT MAX(rev) FROM document_revisions WHERE document_id = 123;
INSERT INTO document_revisions(rev) VALUES(current_rev + 1);
SELECT pg_advisory_unlock(id, 1) FROM documents WHERE id = 123;
Może nie jestem przyzwyczajony do PostgreSQL, a SERIAL może mieć zakres, a może sekwencja i nextval()
lepiej by to zrobiła?
postgresql
locking
Julien Portalier
źródło
źródło
Odpowiedzi:
Zakładając, że przechowujesz wszystkie wersje dokumentu w tabeli, podejście polegałoby na tym, aby nie przechowywać numeru wersji, ale obliczać go na podstawie liczby wersji zapisanych w tabeli.
Zasadniczo jest to wartość pochodna , a nie coś, co trzeba przechowywać.
Do obliczenia numeru wersji można użyć funkcji okna, coś w rodzaju
a będziesz potrzebować kolumny przypominającej
change_date
porządek poprawek.Z drugiej strony, jeśli masz tylko
revision
właściwość dokumentu i wskazuje on „ile razy dokument się zmienił”, to wybrałbym optymistyczne podejście do blokowania, takie jak:Jeśli to aktualizuje 0 wierszy, oznacza to, że nastąpiła aktualizacja pośrednia i musisz o tym poinformować użytkownika.
Ogólnie staraj się, aby Twoje rozwiązanie było tak proste, jak to możliwe. W tym przypadku przez
update
wyrażenia zamiastselect
znakuinsert
lubupdate
źródło
SEKWENCJA gwarantuje, że jest wyjątkowa, a Twój przypadek użycia wygląda na odpowiedni, jeśli liczba dokumentów nie jest zbyt wysoka (w przeciwnym razie masz wiele sekwencji do zarządzania). Użyj klauzuli RETURNING, aby uzyskać wartość wygenerowaną przez sekwencję. Na przykład używając „A36” jako id_dokumentu:
Zarządzanie sekwencjami będzie wymagało szczególnej ostrożności. Być może możesz zachować oddzielną tabelę zawierającą nazwy dokumentów i sekwencję z nimi związaną,
document_id
do której można się odwoływać podczas wstawiania / aktualizowaniadocument_revisions
tabeli.źródło
Często rozwiązuje się to poprzez optymistyczne blokowanie:
Jeśli aktualizacja zwróci 0 zaktualizowanych wierszy, przegapiłeś aktualizację, ponieważ ktoś już ją zaktualizował.
źródło
(Przyszedłem do tego pytania, próbując ponownie odkryć artykuł na ten temat. Teraz, gdy go znalazłem, zamieszczam go tutaj, na wypadek, gdyby inni szukali alternatywy dla obecnie wybranej odpowiedzi - okienko z
row_number()
)Mam ten sam przypadek użycia. Dla każdego rekordu wstawionego do konkretnego projektu w naszym SaaS potrzebujemy unikalnej, rosnącej liczby, która może być wygenerowana w obliczu równoczesnych
INSERT
s i jest idealnie bez przerwy.W tym artykule opisano fajne rozwiązanie , które streszczę tutaj dla ułatwienia i potomności.
document_id
icounter
.counter
będzieDEFAULT 0
Alternatywnie, jeśli masz jużdocument
encję, która grupuje wszystkie wersje,counter
można tam dodać a.BEFORE INSERT
wyzwalacz dodocument_versions
tabeli, która atomowo zwiększa licznik (UPDATE document_revision_counters SET counter = counter + 1 WHERE document_id = ? RETURNING counter
), a następnie ustawiaNEW.version
tę wartość licznika.Alternatywnie możesz użyć CTE, aby to zrobić w warstwie aplikacji (chociaż wolę, aby był wyzwalaczem ze względu na spójność):
Zasadniczo jest to podobne do tego, w jaki sposób próbowałeś go rozwiązać na początku, z tą różnicą, że modyfikując wiersz licznika w pojedynczej instrukcji, blokuje odczyty nieaktualnej wartości, aż do zatwierdzenia
INSERT
.Oto zapis
psql
pokazujący to w akcji:Jak widać, musisz uważać na
INSERT
to, co się dzieje, stąd wersja wyzwalacza, która wygląda następująco:To sprawia, że
INSERT
s jest znacznie prostszy, a integralność danych bardziej niezawodna w porównaniu z danymiINSERT
pochodzącymi z dowolnych źródeł:źródło