Обидва вирішують «вставити або замінити», але REPLACE робить це грубо: при конфлікті унікального ключа він видаляє старий рядок і вставляє новий.
REPLACE INTO settings (user_id, theme) VALUES (1, 'dark');
Наслідки, що роблять REPLACE небезпечним:
- Нове значення
AUTO_INCREMENT. Якщо ключ конфлікту - неid, рядок отримає новийid. Посилання на старийidз інших таблиць ламаються. - Каскадні видалення. Зовнішні ключі з
ON DELETE CASCADEвидалять залежні рядки - «оновлення» налаштувань може знищити пов'язані дані. - Тригери
DELETEспрацьовують, хоча логічно це оновлення. - Втрата колонок. Колонки, яких немає в
REPLACE, отримують значення за замовчуванням: усе, що було в старому рядку, зникає. - Два рядки замість одного при конфлікті за кількома ключами:
REPLACEвидалить усі рядки, з якими конфліктує.
INSERT ... ON DUPLICATE KEY UPDATE оновлює наявний рядок на місці: id той самий, інші колонки не змінюються, каскади не спрацьовують, змінюється рівно те, що вказано.
INSERT INTO settings (user_id, theme) VALUES (1, 'dark') AS new
ON DUPLICATE KEY UPDATE theme = new.theme;
Ще варіант - INSERT IGNORE: вставити, якщо немає, і нічого не робити, якщо є. Але IGNORE перетворює на попередження й інші помилки (обрізання значень, неправильні типи), тож ховає баги.
Правило: INSERT ... ON DUPLICATE KEY UPDATE за замовчуванням. REPLACE - лише для таблиць без залежностей, де повна заміна рядка - саме те, що потрібно (кеш-таблиці, денормалізовані знімки).