9.5 и новее:
PostgreSQL 9.5 и более новая поддержка INSERT ... ON CONFLICT UPDATE
(и ON CONFLICT DO NOTHING
), т.е. upsert.
Сравнение сON DUPLICATE KEY UPDATE
.
Быстрое объяснение .
Для использования см. Руководство - в частности, предложение конфликта_на синтаксической диаграмме и пояснительный текст .
В отличие от решений для 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
TEMPORARY
стол
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;
Связанное чтение
Что о MERGE
?
SQL-стандарт MERGE
самом деле имеет плохо определенную семантику параллелизма и не подходит для загрузки без предварительной блокировки таблицы.
Это действительно полезный оператор OLAP для объединения данных, но на самом деле он не является полезным решением для безопасного параллелизма. Есть много советов людям, использующим другие СУБД для использованияMERGE
в upserts, но это на самом деле неправильно.
Другие БД: