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
s; інший отримує дублюючу помилку ключа і робить 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
для оновлення, є багато порад , але насправді це неправильно.
Інші БД: