INSERT ... ON DUPLICATE KEY UPDATE

Знакомимся с upsert по-MySQL: одна команда «вставь, а если уже есть — обнови».

Upsert — операция «вставить новую строку или, если такая уже существует, обновить её», выполняемая одним запросом.

Зачем это нужно

Частая задача: «увеличить счётчик просмотров» или «сохранить настройку». Если делать вручную, придётся сначала SELECT, проверить наличие, потом INSERT или UPDATE — три шага и гонка состояний при параллельных запросах. MySQL решает это одной командой.

ON DUPLICATE KEY UPDATE

Конструкция работает, когда на таблице есть PRIMARY KEY или UNIQUE-ограничение. При попытке вставить строку, конфликтующую по такому ключу, вместо ошибки выполняется указанное обновление.

CREATE TABLE page_views (
  page VARCHAR(255) PRIMARY KEY,
  views INT NOT NULL DEFAULT 0
);

-- если страницы ещё нет — вставит views=1,
-- если есть — увеличит счётчик на 1
INSERT INTO page_views (page, views)
VALUES ('/home', 1)
ON DUPLICATE KEY UPDATE views = views + 1;

Это MySQL-специфика (для чтения). Функция VALUES(col) внутри части UPDATE даёт значение, которое пытались вставить:

INSERT INTO settings (user_id, theme)
VALUES (42, 'dark')
ON DUPLICATE KEY UPDATE theme = VALUES(theme);

Родственные приёмы

КомандаПоведение при конфликте ключа
INSERTошибка
INSERT IGNOREтихо пропустить конфликтующую строку
INSERT ... ON DUPLICATE KEY UPDATEобновить существующую строку
REPLACEудалить старую строку и вставить новую

Осторожно с REPLACE: он физически удаляет старую строку (срабатывают ON DELETE внешних ключей, теряются незаданные поля) и вставляет заново. ON DUPLICATE KEY UPDATE обычно безопаснее, потому что обновляет на месте.

Что переносимо в песочнице

Сам синтаксис ON DUPLICATE KEY UPDATE — чисто MySQL и в SQLite не выполнится. Но саму идею «было одно значение — стало другое» можно показать обычным UPDATE:

CREATE TABLE page_views (
  page TEXT PRIMARY KEY,
  views INTEGER NOT NULL DEFAULT 0
);

INSERT INTO page_views (page, views) VALUES ('/home', 1);

-- «повторный визит»: имитируем инкремент счётчика
UPDATE page_views SET views = views + 1 WHERE page = '/home';

SELECT page, views FROM page_views;

Вывод:

page   views
/home  2

Итог

  • INSERT ... ON DUPLICATE KEY UPDATE — атомарный upsert по уникальному ключу.
  • Работает только при наличии PRIMARY KEY или UNIQUE на таблице.
  • INSERT IGNORE молча пропускает конфликт, REPLACE удаляет и вставляет заново.
  • REPLACE опаснее: он реально удаляет строку — предпочитайте ON DUPLICATE KEY UPDATE.
Проверьте себя
1. Что делает INSERT ... ON DUPLICATE KEY UPDATE?
Aвсегда вставляет новую строку
Bвставляет строку, а при конфликте по уникальному ключу обновляет существующую
Cудаляет дубликаты
Dсоздаёт уникальный индекс
2. При каком условии работает ON DUPLICATE KEY UPDATE?
Aтаблица должна быть пустой
Bна таблице должен быть PRIMARY KEY или UNIQUE
Cдолжен использоваться движок MyISAM
Dстолбцов должно быть не больше двух
3. Почему REPLACE опаснее, чем ON DUPLICATE KEY UPDATE?
Aон медленнее в тысячу раз
Bон физически удаляет старую строку и вставляет новую, что может сбросить поля и сработать каскадом
Cон не поддерживается в MySQL
Dон не использует ключи
Поддержать проект