Skip to main content

VillageSQL is a drop-in replacement for MySQL with extensions.

All examples in this guide work on VillageSQL. Install Now →
MySQL doesn’t have an UPSERT keyword, but it has three ways to insert a row or handle a conflict: INSERT ... ON DUPLICATE KEY UPDATE, REPLACE INTO, and INSERT IGNORE. They behave very differently — picking the wrong one can silently corrupt data.

INSERT … ON DUPLICATE KEY UPDATE

When the insert would violate a unique constraint (primary key or unique index), MySQL runs the UPDATE instead:
To reference the value from the attempted insert (rather than the existing row), use row aliases:
The older VALUES(col) syntax also works but is deprecated — use row aliases instead:

REPLACE INTO

REPLACE deletes the conflicting row and inserts a new one. It looks like an upsert but behaves like a delete + insert:
This fires BEFORE DELETE and AFTER DELETE triggers on the target table and resets the AUTO_INCREMENT counter for the new row. It does NOT trigger ON DELETE CASCADE rules on related tables — foreign key cascades don’t fire for REPLACE. Any columns not listed in the REPLACE get their default values — not the previous row’s values. Use REPLACE only when you genuinely want to discard the old row entirely.

INSERT IGNORE

INSERT IGNORE silently discards the row if any error occurs during the insert — including duplicate key, type errors, and constraint violations:
The problem with INSERT IGNORE is that it suppresses all errors, not just duplicates. A data type mismatch or a truncation warning becomes a silent no-op. Use it only when you genuinely don’t care about the outcome.

Comparison

AUTO_INCREMENT Side Effect

ON DUPLICATE KEY UPDATE increments the AUTO_INCREMENT counter even when it takes the update path, not the insert path. Over time this creates gaps in the sequence:
This is expected behavior. If your application depends on a gap-free sequence, use a separate counter table or handle sequence generation in the application.

Frequently Asked Questions

Is ON DUPLICATE KEY UPDATE atomic?

Yes — the insert attempt and the update happen in a single atomic operation. You don’t need to wrap it in a transaction to avoid a race condition between checking existence and inserting.

What if multiple unique indexes conflict?

If an insert violates more than one unique constraint simultaneously, MySQL updates only one row — which one is undefined. Avoid designs where a single INSERT can conflict on multiple unique indexes. If your schema has overlapping unique constraints, test the behavior carefully.

Can I use ON DUPLICATE KEY UPDATE with multi-row inserts?

Yes:
Each conflicting row triggers its own update. With row aliases, use VALUES for the multi-row case and AS new for the alias.

Troubleshooting

See also