9.5 и по-нови:
PostgreSQL 9.5 и по-нова поддръжка INSERT ... ON CONFLICT (key) DO UPDATE
(и ON CONFLICT (key) DO NOTHING
), т.е. нагоре.
Сравнение с ON DUPLICATE KEY UPDATE
.
Бързо обяснение.
За употреба вижте ръководството - по-специално conflict_action клауза в синтактичната диаграма и обяснителния текст.
За разлика от решенията за 9.4 и по-стари, които са дадени по-долу, тази функция работи с множество конфликтни реда и не изисква изключително заключване или цикъл за повторен опит.
Комитът за добавяне на функцията е тук, а дискусията около нейното развитие е тук.
Ако сте на 9.5 и не е необходимо да имате обратна съвместимост, можете да спрете да четете сега .
9.4 и по-стари:
PostgreSQL няма вграден UPSERT
(или MERGE
) съоръжение и е много трудно да го направите ефективно в условията на едновременна употреба.
Тази статия разглежда проблема с полезни подробности.
По принцип трябва да избирате между две опции:
- Отделни операции за вмъкване/актуализация в цикъл за повторен опит; или
- Заключване на таблицата и извършване на групово сливане
Цикъл за повторен опит за отделен ред
Използването на отделни прехвърляния на ред в цикъл за повторен опит е разумната опция, ако искате много връзки едновременно да се опитват да извършват вмъквания.
Документацията на PostgreSQL съдържа полезна процедура, която ще ви позволи да направите това в цикъл в базата данни. Той предпазва от загубени актуализации и състезания за вмъкване, за разлика от повечето наивни решения. Ще работи само в READ COMMITTED
режим и е безопасен само ако това е единственото нещо, което правите в транзакцията. Функцията няма да работи правилно, ако тригери или вторични уникални ключове причинят уникални нарушения.
Тази стратегия е много неефективна. Когато е практично, трябва да наредите работа на опашка и вместо това да извършите групово прехвърляне, както е описано по-долу.
Много опитни решения на този проблем не отчитат връщане назад, така че водят до непълни актуализации. Две транзакции се състезават една с друга; един от тях успешно INSERT
с; другият получава грешка с дублиран ключ и прави UPDATE
вместо. UPDATE
блокове, чакащи INSERT
за връщане назад или ангажиране. Когато се върне назад, UPDATE
повторната проверка на условието съответства на нула редове, така че въпреки че UPDATE
обвързва, че всъщност не е направило разстройството, което сте очаквали. Трябва да проверите броя на редовете с резултати и да опитате отново, където е необходимо.
Някои опитни решения също не успяват да вземат предвид състезанията SELECT. Ако опитате очевидното и просто:
-- 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;
тогава когато двама работят наведнъж, има няколко режима на отказ. Единият е вече обсъжданият проблем с повторна проверка на актуализацията. Друго е, където и двете UPDATE
в същото време, съвпадение на нула редове и продължаване. След това и двамата правят EXISTS
тест, който се случва преди INSERT
. И двете получават нулеви редове, така че и двете правят INSERT
. Единият се проваля с грешка с дублиран ключ.
Ето защо имате нужда от цикъл за повторен опит. Може да си мислите, че можете да предотвратите дублиращи се ключови грешки или загубени актуализации с интелигентен SQL, но не можете. Трябва да проверите броя на редовете или да се справите с дублиращи се ключови грешки (в зависимост от избрания подход) и да опитате отново.
Моля, не предлагайте собствено решение за това. Както при опашката за съобщения, вероятно е погрешно.
Насипно прехвърляне със заключване
Понякога искате да направите групово прехвърляне, при което имате нов набор от данни, който искате да обедините в по-стар съществуващ набор от данни. Това е изключително по-ефективен от преместването на отделни редове и трябва да се предпочита, когато е практично.
В този случай обикновено следвате следния процес:
-
CREATE
aTEMPORARY
таблица -
COPY
или групово вмъкнете новите данни във временната таблица -
LOCK
целевата таблицаIN EXCLUSIVE MODE
. Това позволява на други транзакции даSELECT
, но не правете никакви промени в таблицата. -
Направете
UPDATE ... FROM
на съществуващи записи, използващи стойностите във временната таблица; -
Направете
INSERT
от редове, които все още не съществуват в целевата таблица; -
COMMIT
, освобождаване на ключалката.
Например, за примера, даден във въпроса, използвайки многозначно INSERT
за да попълните временната таблица:
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;
Свързано четене
- UPSERT уики страница
- UPSERTизми в Postgres
- Вмъкване при дублирана актуализация в PostgreSQL?
- http://petereisentraut.blogspot.com/2010/05/merge-syntax.html
- Направете с транзакция
- Изберете или INSERT във функция предразположени ли са условия на състезание?
- SQL
MERGE
в уикито на PostgreSQL - Най-идиоматичният начин за внедряване на UPSERT в Postgresql в днешно време
Ами MERGE
?
SQL-стандарт MERGE
всъщност има лошо дефинирана семантика на паралелност и не е подходящ за добавяне без първо заключване на таблица.
Това е наистина полезен оператор на OLAP за обединяване на данни, но всъщност не е полезно решение за безопасно прехвърляне на паралелност. Има много съвети към хората, които използват други СУБД, да използват MERGE
за разстройства, но всъщност е погрешно.
Други БД:
INSERT ... ON DUPLICATE KEY UPDATE
в MySQLMERGE
от MS SQL Server (но вижте по-горе заMERGE
проблеми)MERGE
от Oracle (но вижте по-горе заMERGE
проблеми)