Bardzo często zadawanym pytaniem jest tutaj, jak zrobić upsert, czyli to, co wywołuje MySQL, INSERT ... ON DUPLICATE UPDATE
a standardowe obsługuje jako częśćMERGE
operacji.
Biorąc pod uwagę, że PostgreSQL nie obsługuje go bezpośrednio (przed pg 9.5), jak to zrobić? Rozważ następujące:
CREATE TABLE testtable (
id integer PRIMARY KEY,
somedata text NOT NULL
);
INSERT INTO testtable (id, somedata) VALUES
(1, 'fred'),
(2, 'bob');
Teraz wyobraź sobie, że chcesz „upsert” krotek (2, 'Joe')
, (3, 'Alan')
tak nowe zawartość tabeli będzie:
(1, 'fred'),
(2, 'Joe'), -- Changed value of existing tuple
(3, 'Alan') -- Added new tuple
O tym ludzie rozmawiają podczas dyskusji upsert
. Co najważniejsze, każde podejście musi być bezpieczne w obecności wielu transakcji pracujących przy tym samym stole - albo przez jawne blokowanie, albo w inny sposób bronić się przed wynikowymi warunkami wyścigu.
Ten temat jest obszernie omawiany na stronie Insert, przy zduplikowanej aktualizacji w PostgreSQL?, ale chodzi o alternatywy dla składni MySQL, az czasem pojawiło się sporo niepowiązanych szczegółów. Pracuję nad ostatecznymi odpowiedziami.
Techniki te są również przydatne do „wstaw, jeśli nie istnieje, w przeciwnym razie nic nie rób”, tj. „Wstaw ... przy zduplikowanym kluczu ignoruj”.
źródło
Odpowiedzi:
9.5 i nowsze:
PostgreSQL 9.5 i nowsze wsparcie
INSERT ... ON CONFLICT UPDATE
(iON CONFLICT DO NOTHING
), tj. Upsert.Porównanie z
ON DUPLICATE KEY UPDATE
.Szybkie wyjaśnienie .
Aby zapoznać się z użytkowaniem, zobacz instrukcję - w szczególności klauzulę o konfliktach działań na schemacie składni oraz tekst wyjaśniający .
W przeciwieństwie do rozwiązań dla wersji 9.4 i starszych, które podano poniżej, ta funkcja działa z wieloma sprzecznymi wierszami i nie wymaga wyłącznego blokowania ani ponownej próby.
Zatwierdzenie dodające funkcję jest tutaj, a dyskusja wokół jej rozwoju jest tutaj .
Jeśli korzystasz z wersji 9.5 i nie musisz być kompatybilny wstecz, możesz teraz przestać czytać .
9.4 i starsze:
PostgreSQL nie ma żadnej wbudowanej
UPSERT
(lubMERGE
) funkcji, a robienie tego skutecznie w obliczu równoczesnego użycia jest bardzo trudne.W tym artykule szczegółowo omówiono problem .
Ogólnie rzecz biorąc, musisz wybrać jedną z dwóch opcji:
Pętla ponawiania pojedynczych wierszy
Użycie pojedynczych poprawek wierszy w pętli ponownej próby jest rozsądną opcją, jeśli chcesz, aby wiele połączeń jednocześnie próbowało wykonać wstawienia.
Dokumentacja PostgreSQL zawiera przydatną procedurę, która pozwoli ci to zrobić w pętli wewnątrz bazy danych . Chroni przed utraconymi aktualizacjami i wstawia rasy, w przeciwieństwie do większości naiwnych rozwiązań. Działa tylko w
READ COMMITTED
trybie i jest bezpieczny tylko wtedy, gdy jest to jedyna rzecz, którą robisz w transakcji. Funkcja nie będzie działać poprawnie, jeśli wyzwalacze lub dodatkowe unikalne klucze powodują unikalne naruszenia.Ta strategia jest bardzo nieefektywna. Kiedy tylko jest to praktyczne, powinieneś ustawiać w kolejce prace i wykonać zbiorczy upsert, jak opisano poniżej.
Wiele prób rozwiązania tego problemu nie uwzględnia wycofania, więc powodują niekompletne aktualizacje. Dwie transakcje ścigają się ze sobą; jeden z nich pomyślnie
INSERT
; drugi otrzymuje duplikat błędu klucza iUPDATE
zamiast tego robi błąd . TeUPDATE
bloki czekają naINSERT
wycofywania lub zatwierdzenia. Kiedy się wycofuje,UPDATE
warunek ponownego sprawdzania dopasowuje zero wierszy, więc nawet jeśliUPDATE
zatwierdzenia nie wykonały oczekiwanego upsert. Musisz sprawdzić liczbę wierszy wyników i spróbować ponownie w razie potrzeby.Niektóre próby rozwiązania nie uwzględniają również wyścigów SELECT. Jeśli spróbujesz oczywistego i prostego:
wtedy, gdy dwa działają jednocześnie, istnieje kilka trybów awarii. Jednym z nich jest już omówiony problem z ponownym sprawdzaniem aktualizacji. Innym jest, gdy oba
UPDATE
jednocześnie, dopasowując zero wierszy i kontynuując. Potem oboje zrobićEXISTS
test, który dzieje się przedINSERT
. Oba mają zero wierszy, więc oba robiąINSERT
. Jeden kończy się niepowodzeniem z duplikatem błędu klucza.Dlatego potrzebujesz pętli ponownej próby. Możesz pomyśleć, że możesz zapobiec zduplikowanym kluczowym błędom lub utracie aktualizacji dzięki sprytnemu SQL, ale nie możesz. Musisz sprawdzić liczbę wierszy lub obsłużyć zduplikowane błędy klucza (w zależności od wybranego podejścia) i spróbować ponownie.
Nie rzucaj na to własnym rozwiązaniem. Podobnie jak w przypadku kolejkowania wiadomości, prawdopodobnie jest to złe.
Masywny upsert z zamkiem
Czasami chcesz zrobić zbiorczy upsert, w którym masz nowy zestaw danych, który chcesz scalić ze starszym istniejącym zestawem danych. Jest to znacznie bardziej wydajne niż wstawianie pojedynczych rzędów i powinno być preferowane, gdy jest to praktyczne.
W takim przypadku zazwyczaj postępujesz według następującego procesu:
CREATE
TEMPORARY
stółCOPY
lub wstaw zbiorczo nowe dane do tabeli tempLOCK
tabela docelowaIN EXCLUSIVE MODE
. Pozwala to na inne transakcjeSELECT
, ale nie wprowadza żadnych zmian w tabeli.Wykonaj jeden
UPDATE ... FROM
z istniejących rekordów, używając wartości z tabeli temp;Wykonaj jeden
INSERT
z wierszy, które jeszcze nie istnieją w tabeli docelowej;COMMIT
, zwalniając blokadę.Na przykład w przykładzie podanym w pytaniu, używając wielowartościowego
INSERT
do wypełnienia tabeli temp:Powiązane czytanie
MERGE
na wiki PostgreSQLCo
MERGE
?Standard SQL
MERGE
ma właściwie źle zdefiniowaną semantykę współbieżności i nie nadaje się do upserowania bez uprzedniego zablokowania tabeli.To naprawdę przydatna instrukcja OLAP do łączenia danych, ale tak naprawdę nie jest to przydatne rozwiązanie dla bezpiecznego współbieżności. Istnieje wiele porad dla osób korzystających z innych DBMS do używania
MERGE
upserts, ale tak naprawdę jest to złe.Inne bazy danych:
INSERT ... ON DUPLICATE KEY UPDATE
w MySQLMERGE
z MS SQL Server (ale patrz wyżej oMERGE
problemach)MERGE
z Oracle (ale patrz wyżej oMERGE
problemach)źródło
MERGE
wspomniano powyżej, „rozwiązania” stosowane w SQL Server i Oracle są niepoprawne i podatne na warunki wyścigowe. Musisz dokładnie przyjrzeć się każdemu DBMS, aby dowiedzieć się, jak sobie z nimi poradzić. Naprawdę mogę zaoferować tylko porady na temat PostgreSQL. Jedynym sposobem na wykonanie bezpiecznego wielowierszowego uaktualnienia na PostgreSQL jest dodanie obsługi macierzystego uaktualnienia do serwera głównego.Próbuję wnieść wkład w inne rozwiązanie problemu pojedynczego wstawiania w wersjach PostgreSQL wcześniejszych niż 9.5. Chodzi o to, aby po prostu spróbować wykonać najpierw wstawienie, a jeśli rekord już istnieje, zaktualizuj go:
Pamiętaj, że to rozwiązanie można zastosować tylko wtedy, gdy nie zostaną usunięte wiersze tabeli .
Nie wiem o skuteczności tego rozwiązania, ale wydaje mi się to dość rozsądne.
źródło
insert on update
Oto kilka przykładów
insert ... on conflict ...
( str. 9.5+ ):źródło
Wprowadzenie SQLAlchemy dla Postgres> = 9.5
Ponieważ powyższy duży post obejmuje wiele różnych podejść SQL do wersji Postgres (nie tylko nie-9.5 jak w pytaniu), chciałbym dodać, jak to zrobić w SQLAlchemy, jeśli używasz Postgres 9.5. Zamiast implementować własny upsert, możesz także użyć funkcji SQLAlchemy (które zostały dodane w SQLAlchemy 1.1). Osobiście polecam korzystanie z nich, jeśli to możliwe. Nie tylko ze względu na wygodę, ale także dlatego, że pozwala PostgreSQLowi obsługiwać wszelkie warunki wyścigowe, które mogą wystąpić.
Cross-posting z innej odpowiedzi, którą wczoraj podałem ( https://stackoverflow.com/a/44395983/2156909 )
SQLAlchemy obsługuje
ON CONFLICT
teraz dwie metodyon_conflict_do_update()
ion_conflict_do_nothing()
:Kopiowanie z dokumentacji:
http://docs.sqlalchemy.org/en/latest/dialects/postgresql.html?highlight=conflict#insert-on-conflict-upsert
źródło
Testowane na Postgresql 9.3
źródło
SERIALIZABLE
izolacji, przerwiesz z niepowodzeniem serializacji, w przeciwnym razie prawdopodobnie dostaniesz wyjątkowe naruszenie. Nie wymyślaj na nowo, ponowne wymyślenie będzie błędne. ZastosowanieINSERT ... ON CONFLICT ...
. Jeśli twój PostgreSQL jest za stary, zaktualizuj go.INSERT ... ON CLONFLICT ...
nie jest przeznaczony do masowego ładowania. Z twojego postuLOCK TABLE testtable IN EXCLUSIVE MODE;
wewnątrz CTE jest obejście, które pozwala uzyskać rzeczy atomowe. Nieinsert ... where not exists ...
podobnego lub podobnego.Ponieważ to pytanie zostało zamknięte, piszę tutaj o tym, jak to zrobić za pomocą SQLAlchemy. Poprzez rekurencję ponawia próbę włożenia lub aktualizacji zbiorczej w celu zwalczania warunków wyścigu i błędów walidacji.
Najpierw import
Teraz działa kilka pomocników
I wreszcie funkcja upsert
Oto jak z niego korzystasz
Zaletą tego jest
bulk_save_objects
to, że może obsługiwać relacje, sprawdzanie błędów itp. Podczas wstawiania (w przeciwieństwie do operacji masowych ).źródło
SERIALIZABLE
transakcji i obsługiwać błędy serializacji, ale jest to powolne. Potrzebujesz obsługi błędów i ponownej próby. Zobacz moją odpowiedź i sekcję „powiązane czytanie”.