Очень часто задаваемый вопрос здесь - как сделать upsert, что вызывает MySQL INSERT ... ON DUPLICATE UPDATE
и поддерживает стандарт как часть MERGE
операции.
Учитывая, что PostgreSQL не поддерживает его напрямую (до pg 9.5), как вы это делаете? Учтите следующее:
CREATE TABLE testtable (
id integer PRIMARY KEY,
somedata text NOT NULL
);
INSERT INTO testtable (id, somedata) VALUES
(1, 'fred'),
(2, 'bob');
Теперь представьте , что вы хотите «upsert» кортежи (2, 'Joe')
, (3, 'Alan')
так что новое содержимое таблицы будет выглядеть так :
(1, 'fred'),
(2, 'Joe'), -- Changed value of existing tuple
(3, 'Alan') -- Added new tuple
Это то, о чем говорят люди, когда обсуждают upsert
. Важно отметить, что любой подход должен быть безопасным при наличии нескольких транзакций, работающих на одной и той же таблице - либо с помощью явной блокировки, либо иным образом защищаясь от возникающих условий гонки.
Эта тема широко обсуждается на Вставке, на дубликате обновления в PostgreSQL? , но это об альтернативах синтаксису MySQL, и с течением времени он вырос довольно много не связанных деталей. Я работаю над окончательными ответами.
Эти методы также полезны для «вставить, если не существует, в противном случае ничего не делать», то есть «вставить ... при игнорировании дубликата ключа».
источник
Ответы:
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. Если вы попробуете очевидное и простое:
затем, когда два запускаются одновременно, есть несколько режимов отказа. Одним из них является уже обсуждаемая проблема с перепроверкой обновлений. Другой - где оба
UPDATE
одновременно совпадают с нулевыми строками и продолжают. Затем они оба делаютEXISTS
испытание, которое происходит доINSERT
того . Оба получают ноль строк, поэтому оба делаютINSERT
. Один не удается с ошибкой дубликата ключа.Вот почему вам нужен повторный цикл. Вы можете подумать, что вы можете предотвратить повторяющиеся ошибки ключа или потерянные обновления с помощью умного SQL, но вы не можете. Вам необходимо проверить количество строк или обработать дубликаты ошибок ключа (в зависимости от выбранного подхода) и повторить попытку.
Пожалуйста, не катите свое собственное решение для этого. Как и в случае с очередями сообщений, это, вероятно, неправильно.
Массовый уперт с замком
Иногда вы хотите выполнить массовое обновление, где у вас есть новый набор данных, который вы хотите объединить в более старый существующий набор данных. Это значительно эффективнее, чем отдельные аппроциклы, и должно быть предпочтительным, когда это целесообразно.
В этом случае вы обычно выполняете следующий процесс:
CREATE
TEMPORARY
столCOPY
или массово вставить новые данные во временную таблицуLOCK
целевой столIN EXCLUSIVE MODE
. Это разрешает другие транзакцииSELECT
, но не вносит никаких изменений в таблицу.Сделайте
UPDATE ... FROM
из существующих записей, используя значения во временной таблице;Сделайте
INSERT
из строк, которые еще не существуют в целевой таблице;COMMIT
, открыв замок.Например, для примера, приведенного в вопросе, используя многозначное значение
INSERT
для заполнения временной таблицы:Связанное чтение
MERGE
на PostgreSQL викиЧто о
MERGE
?SQL-стандарт
MERGE
самом деле имеет плохо определенную семантику параллелизма и не подходит для загрузки без предварительной блокировки таблицы.Это действительно полезный оператор OLAP для объединения данных, но на самом деле он не является полезным решением для безопасного параллелизма. Есть много советов людям, использующим другие СУБД для использования
MERGE
в upserts, но это на самом деле неправильно.Другие БД:
INSERT ... ON DUPLICATE KEY UPDATE
в MySQLMERGE
из MS SQL Server (но см. выше оMERGE
проблемах)MERGE
от Oracle (но см. выше оMERGE
проблемах)источник
MERGE
отмечалось выше, «решения», которые используются для SQL Server и Oracle, являются неправильными и подвержены условиям гонки. Вам нужно будет изучить каждую СУБД, чтобы выяснить, как с ними обращаться, я могу только дать совет по PostgreSQL. Единственный способ сделать безопасное многострочное upsert на PostgreSQL - это добавить поддержку основного upsert на главный сервер.Я пытаюсь внести свой вклад в другое решение для одной проблемы вставки с версиями PostgreSQL до 9.5. Идея состоит в том, чтобы просто попытаться сначала выполнить вставку, и, если запись уже существует, обновить ее:
Обратите внимание, что это решение может быть применено, только если нет удаления строк таблицы .
Я не знаю об эффективности этого решения, но оно кажется мне достаточно разумным.
источник
insert on update
Вот несколько примеров
insert ... on conflict ...
( стр 9.5+ ):источник
Поддержка SQLAlchemy для Postgres> = 9.5
Поскольку большой пост, описанный выше, охватывает множество различных подходов SQL для версий Postgres (не только не-9.5, как в вопросе), я хотел бы добавить, как это сделать в SQLAlchemy, если вы используете Postgres 9.5. Вместо того, чтобы реализовывать свой собственный эффект, вы также можете использовать функции SQLAlchemy (которые были добавлены в SQLAlchemy 1.1). Лично я бы порекомендовал использовать их, если это возможно. Не только из-за удобства, но и потому, что он позволяет PostgreSQL обрабатывать любые условия гонки, которые могут возникнуть.
Перекрестная публикация из другого ответа, который я дал вчера ( https://stackoverflow.com/a/44395983/2156909 )
SQLAlchemy
ON CONFLICT
теперь поддерживает два методаon_conflict_do_update()
иon_conflict_do_nothing()
:Копирование из документации:
http://docs.sqlalchemy.org/en/latest/dialects/postgresql.html?highlight=conflict#insert-on-conflict-upsert
источник
Протестировано на Postgresql 9.3
источник
SERIALIZABLE
изоляцию, вы получите прерывание с ошибкой сериализации, иначе вы, вероятно, получите уникальное нарушение. Не изобретай заново, повторное изобретение будет ошибочным. ИспользованиеINSERT ... ON CONFLICT ...
. Если ваш PostgreSQL слишком старый, обновите его.INSERT ... ON CLONFLICT ...
не предназначен для массовой загрузки. Из вашего поста,LOCK TABLE testtable IN EXCLUSIVE MODE;
внутри CTE есть обходной путь для получения атомарных вещей. Нет?insert ... where not exists ...
аналогичное действие.Поскольку этот вопрос был закрыт, я публикую здесь о том, как вы делаете это с помощью SQLAlchemy. Через рекурсию он повторяет массовую вставку или обновление для борьбы с условиями гонки и ошибками проверки.
Сначала импорт
Теперь пара вспомогательных функций
И, наконец, функция upsert
Вот как ты это используешь
Преимущество этого в
bulk_save_objects
том, что он может обрабатывать отношения, проверку ошибок и т. Д. При вставке (в отличие от массовых операций ).источник
SERIALIZABLE
транзакции и обрабатывать ошибки сериализации, но это медленно. Вам нужна обработка ошибок и повторный цикл. См. Мой ответ и раздел «связанное чтение».