У меня есть следующая таблица счетчиков:
CREATE TABLE cache (
key text PRIMARY KEY,
generation int
);
Я хотел бы увеличить один из счетчиков или установить его на ноль, если соответствующая строка еще не существует. Есть ли способ сделать это без проблем с параллелизмом в стандартном SQL? Операция иногда является частью транзакции, иногда отдельной.
Если возможно, SQL должен работать без изменений на SQLite, PostgreSQL и MySQL.
Поиск дал несколько идей, которые либо страдают проблемами параллелизма, либо относятся к базе данных:
Попробуйте
INSERT
новую строку, иUPDATE
не было ли ошибки. К сожалению, ошибкаINSERT
прерывает текущую транзакцию.UPDATE
строку и, если строки не были изменены,INSERT
новую строку.В MySQL есть
ON DUPLICATE KEY UPDATE
пункт.
РЕДАКТИРОВАТЬ: Спасибо за все отличные ответы. Похоже, Пол прав, и нет единого портативного способа сделать это. Для меня это довольно удивительно, так как это звучит как очень простая операция.
Ответы:
MySQL (а затем и SQLite) также поддерживают синтаксис REPLACE INTO:
REPLACE INTO my_table (pk_id, col1) VALUES (5, '123');
Это автоматически определяет первичный ключ и находит соответствующую строку для обновления, вставляя новую, если таковая не найдена.
источник
SQLite поддерживает замену строки, если она уже существует:
INSERT OR REPLACE INTO [...blah...]
Вы можете сократить это до
REPLACE INTO [...blah...]
Этот ярлык был добавлен для совместимости с
REPLACE INTO
выражением MySQL .источник
PRAMARY KEY
в своих values.Я бы сделал что-то вроде следующего:
INSERT INTO cache VALUES (key, generation) ON DUPLICATE KEY UPDATE (key = key, generation = generation + 1);
Установка значения генерации на 0 в коде или в sql, но использование ON DUP ... для увеличения значения. В любом случае, я думаю, что это синтаксис.
источник
предложение ON DUPLICATE KEY UPDATE - лучшее решение, потому что: REPLACE выполняет DELETE, за которым следует INSERT, поэтому в течение очень небольшого периода запись удаляется, создавая очень небольшую вероятность того, что запрос может вернуться, пропустив это, если страница была просматривается во время запроса REPLACE.
Я предпочитаю INSERT ... ON DUPLICATE UPDATE ... по этой причине.
Решение jmoz является лучшим: хотя я предпочитаю синтаксис SET круглым скобкам
INSERT INTO cache SET key = 'key', generation = 'generation' ON DUPLICATE KEY UPDATE key = 'key', generation = (generation + 1) ;
источник
Я не знаю, что вы найдете решение, нейтральное к платформе.
Обычно это называется «UPSERT».
См. Некоторые связанные обсуждения:
источник
В PostgreSQL нет команды слияния, и на самом деле написать ее нетривиально - на самом деле есть странные крайние случаи, которые делают задачу «интересной».
Наилучший (например, при работе в наиболее возможных условиях) подход - использовать функцию - такую, как показано в руководстве (merge_db).
Если вы не хотите использовать функцию, обычно можно обойтись:
updated = db.execute(UPDATE ... RETURNING 1) if (!updated) db.execute(INSERT...)
Просто помните, что это не защита от ошибок, и в конечном итоге она выйдет из строя.
источник
Стандартный SQL предоставляет для этой задачи оператор MERGE. Не все СУБД поддерживают оператор MERGE.
источник
Если у вас нет обычного способа атомарного обновления или вставки (например, с помощью транзакции), вы можете вернуться к другой схеме блокировки. 0-байтовый файл, системный мьютекс, именованный канал и т. Д.
источник
Не могли бы вы использовать триггер вставки? В случае неудачи сделайте обновление.
источник
Если вы согласны с использованием библиотеки, которая пишет за вас SQL, вы можете использовать Upsert (в настоящее время только Ruby и Python):
Это работает в MySQL, Postgres и SQLite3.
Он пишет хранимую процедуру или пользовательскую функцию (UDF) в MySQL и Postgres. Он используется
INSERT OR REPLACE
в SQLite3.источник