9,5 i nowsze:
PostgreSQL 9.5 i nowsze wspierają INSERT ... ON CONFLICT (key) DO UPDATE
(i ON CONFLICT (key) DO NOTHING
), czyli upsert.
Porównanie z ON DUPLICATE KEY UPDATE
.
Szybkie wyjaśnienie.
Aby dowiedzieć się więcej, zapoznaj się z instrukcją, a konkretnie conflict_action klauzulę na diagramie składni i tekst objaś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 pętli ponawiania.
Zatwierdzenie dodawania funkcji jest tutaj, a dyskusja na temat jej rozwoju jest tutaj.
Jeśli korzystasz z wersji 9.5 i nie musisz mieć wstecznej kompatybilności, możesz przestać czytać teraz .
9.4 i starsze:
PostgreSQL nie ma wbudowanego UPSERT
(lub MERGE
), a robienie tego skutecznie w obliczu równoczesnego użytkowania jest bardzo trudne.
W tym artykule szczegółowo omówiono problem.
Zasadniczo musisz wybrać jedną z dwóch opcji:
- Pojedyncze operacje wstawiania/aktualizowania w pętli ponawiania; lub
- Blokowanie tabeli i wykonywanie scalania wsadowego
Pętla ponawiania poszczególnych wierszy
Używanie upsertów pojedynczych wierszy w pętli ponawiania jest rozsądną opcją, jeśli chcesz, aby wiele połączeń jednocześnie próbowało wykonać wstawianie.
Dokumentacja PostgreSQL zawiera przydatną procedurę, która pozwoli Ci to zrobić w pętli wewnątrz bazy danych. Chroni przed zgubionymi aktualizacjami i wyścigami wstawiania, w przeciwieństwie do większości naiwnych rozwiązań. Będzie działać tylko w READ COMMITTED
tryb 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 drugorzędne unikalne klucze powodują unikalne naruszenia.
Ta strategia jest bardzo nieefektywna. Gdy tylko jest to praktyczne, powinieneś ustawić się w kolejce i zamiast tego wykonać zbiorcze upsert, jak opisano poniżej.
Wiele prób rozwiązania tego problemu nie uwzględnia wycofywania, więc skutkują niekompletnymi aktualizacjami. Dwie transakcje ścigają się ze sobą; jeden z nich pomyślnie INSERT
s; drugi otrzymuje błąd zduplikowanego klucza i wykonuje UPDATE
zamiast. UPDATE
bloki czekające na INSERT
wycofać lub zatwierdzić. Po wycofaniu UPDATE
ponowne sprawdzenie warunku pasuje do zera wierszy, więc nawet jeśli UPDATE
zatwierdzeń, w rzeczywistości nie zrobił tego, czego się spodziewałeś. Musisz sprawdzić liczbę wierszy wyników i w razie potrzeby spróbować ponownie.
Niektóre próby rozwiązań również nie uwzględniają wyścigów SELECT. Jeśli spróbujesz oczywistego i prostego:
-- THIS IS WRONG. DO NOT COPY IT. It's an EXAMPLE.
BEGIN;
UPDATE testtable
SET somedata = 'blah'
WHERE id = 2;
-- Remember, this is WRONG. Do NOT COPY IT.
INSERT INTO testtable (id, somedata)
SELECT 2, 'blah'
WHERE NOT EXISTS (SELECT 1 FROM testtable WHERE testtable.id = 2);
COMMIT;
wtedy, gdy dwa biegną jednocześnie, istnieje kilka trybów awarii. Jednym z nich jest już omawiany problem z ponownym sprawdzeniem aktualizacji. Innym jest miejsce, w którym zarówno UPDATE
w tym samym czasie, dopasowując zero wierszy i kontynuując. Następnie oboje wykonują EXISTS
test, który odbywa się przed INSERT
. Oba otrzymują zero wierszy, więc oba wykonują INSERT
. Jeden kończy się niepowodzeniem z powodu błędu zduplikowanego klucza.
Dlatego potrzebujesz pętli ponownej próby. Możesz pomyśleć, że możesz zapobiec zduplikowanym błędom kluczy lub utraconym aktualizacjom za pomocą sprytnego SQL, ale nie możesz. Musisz sprawdzić liczbę wierszy lub obsłużyć zduplikowane błędy kluczy (w zależności od wybranego podejścia) i spróbować ponownie.
Proszę nie wprowadzać własnego rozwiązania w tym zakresie. Podobnie jak w przypadku kolejkowania wiadomości, prawdopodobnie jest źle.
Wkładka zbiorcza z zamkiem
Czasami chcesz wykonać zbiorcze upsert, w którym masz nowy zestaw danych, który chcesz scalić ze starszym istniejącym zestawem danych. To jest ogromne bardziej wydajne niż pojedyncze rzędy upsert i powinny być preferowane, gdy tylko jest to możliwe.
W takim przypadku zazwyczaj postępuj zgodnie z następującym procesem:
-
CREATE
TEMPORARY
stół -
COPY
lub zbiorczo wstaw nowe dane do tabeli tymczasowej -
LOCK
tabela docelowaIN EXCLUSIVE MODE
. Pozwala to innym transakcjom naSELECT
, ale nie wprowadzaj żadnych zmian w tabeli. -
Wykonaj
UPDATE ... FROM
istniejących rekordów przy użyciu wartości z tabeli tymczasowej; -
Wykonaj
INSERT
wierszy, które jeszcze nie istnieją w tabeli docelowej; -
COMMIT
, zwalniając blokadę.
Na przykład dla przykładu podanego w pytaniu przy użyciu wielowartościowego INSERT
aby wypełnić tabelę tymczasową:
BEGIN;
CREATE TEMPORARY TABLE newvals(id integer, somedata text);
INSERT INTO newvals(id, somedata) VALUES (2, 'Joe'), (3, 'Alan');
LOCK TABLE testtable IN EXCLUSIVE MODE;
UPDATE testtable
SET somedata = newvals.somedata
FROM newvals
WHERE newvals.id = testtable.id;
INSERT INTO testtable
SELECT newvals.id, newvals.somedata
FROM newvals
LEFT OUTER JOIN testtable ON (testtable.id = newvals.id)
WHERE testtable.id IS NULL;
COMMIT;
Powiązane czytanie
- UPSERT strona wiki
- UPSERTyzmy w Postgresie
- Wstawić, przy zduplikowanej aktualizacji w PostgreSQL?
- http://petereisentraut.blogspot.com/2010/05/merge-syntax.html
- Zamęt z transakcją
- Czy SELECT lub INSERT w funkcji podatnej na wyścigi?
- SQL
MERGE
na wiki PostgreSQL - Najbardziej idiomatyczny sposób implementacji UPSERT w Postgresql w dzisiejszych czasach
A co z MERGE
?
Standard SQL MERGE
w rzeczywistości ma słabo zdefiniowaną semantykę współbieżności i nie nadaje się do wstawiania bez uprzedniego zablokowania tabeli.
Jest to naprawdę przydatne oświadczenie OLAP do scalania danych, ale w rzeczywistości nie jest to przydatne rozwiązanie do wstawiania bezpiecznych współbieżności. Istnieje wiele rad dla osób korzystających z innych DBMS, aby używać MERGE
dla upsertów, ale w rzeczywistości jest źle.
Inne bazy danych:
INSERT ... ON DUPLICATE KEY UPDATE
w MySQLMERGE
z MS SQL Server (ale patrz wyżej oMERGE
problemy)MERGE
z Oracle (ale zobacz powyżej oMERGE
problemy)