INSERT IGNORE vs INSERT ... ON DUPLICATE KEY UPDATE
System Design practice on Codemia
Work through 120+ system design problems with detailed solutions, from rate limiters to multi-region storage.
Introduction
INSERT IGNORE silently skips rows that would violate a unique or primary key constraint. INSERT ... ON DUPLICATE KEY UPDATE detects the conflict and updates the existing row instead. Choose INSERT IGNORE when duplicate rows can be safely discarded. Choose ON DUPLICATE KEY UPDATE when you need to merge new data into existing rows.
INSERT IGNORE: Skip Duplicates Silently
Syntax
How it works
- MySQL attempts the INSERT.
- If a duplicate key violation occurs, the row is silently discarded.
- No error is raised. The statement continues to the next row.
- The auto-increment counter still advances, creating gaps in IDs.
Example
Batch insert with IGNORE
INSERT ... ON DUPLICATE KEY UPDATE: Upsert
Syntax
How it works
- MySQL attempts the INSERT.
- If a duplicate key violation occurs, MySQL runs the UPDATE clause on the existing row.
- The
VALUES()function (deprecated in MySQL 8.0.20+) or row alias references the values from the attempted INSERT. - The affected-rows count returns 1 for an insert, 2 for an update, and 0 if the row existed but no values changed.
Example
MySQL 8.0.20+ syntax (row alias)
The VALUES() function in the UPDATE clause is deprecated since MySQL 8.0.20. Use a row alias instead:
Conditional update (update only if value changed)
This updates the timestamp only when the price actually changes, avoiding unnecessary writes.
Counter/aggregation pattern
A common use case is incrementing a counter:
Each call either creates the row with view_count = 1 or increments the existing count.
Side-by-Side Comparison
| Behavior | INSERT IGNORE | ON DUPLICATE KEY UPDATE |
| On duplicate | Silently skips the row | Updates the existing row |
| Data from new row | Discarded | Available for the UPDATE clause |
| Error raised | No | No |
| Non-duplicate errors | Converted to warnings | Normal errors |
| Auto-increment gaps | Yes (counter advances even on skip) | Yes (counter advances) |
| Affected rows on duplicate | 0 | 2 (update) or 0 (no change) |
| Performance (high conflict) | Faster (no update I/O) | Slower (writes to existing row) |
| Performance (low conflict) | Similar | Similar |
| Data integrity risk | Higher (silent discard) | Lower (data is preserved) |
| Use in REPLACE INTO | N/A | Different: REPLACE deletes + inserts |
REPLACE INTO: A Third Option
MySQL also offers REPLACE INTO, which deletes the conflicting row and inserts the new one. This is different from both approaches above:
| Aspect | INSERT IGNORE | ON DUPLICATE KEY UPDATE | REPLACE INTO |
| Existing row | Unchanged | Updated in place | Deleted and re-inserted |
| Auto-increment | Unchanged | Unchanged | New ID assigned |
| ON DELETE triggers | Not fired | Not fired | Fired (row is deleted) |
| Foreign key cascades | Not affected | Not affected | Cascade deletes fire |
| Performance | Fastest | Medium | Slowest (delete + insert) |
Avoid REPLACE INTO when you have foreign keys or auto-increment IDs that other rows reference. The delete-then-insert behavior can trigger cascading deletes.
Real-World Use Cases
Use INSERT IGNORE for: idempotent data loading
Use ON DUPLICATE KEY UPDATE for: syncing external data
Use ON DUPLICATE KEY UPDATE for: session management
Use INSERT IGNORE for: deduplication during migration
Performance Considerations
For bulk operations with many conflicts, the performance difference matters:
These are approximate numbers. Actual performance depends on table structure, index count, and hardware. The key insight: INSERT IGNORE is faster because it skips the update write I/O on conflicts.
For very high throughput, consider batching:
PostgreSQL Equivalent
If you work with PostgreSQL, the equivalent syntax uses ON CONFLICT:
| MySQL | PostgreSQL |
INSERT IGNORE | ON CONFLICT DO NOTHING |
ON DUPLICATE KEY UPDATE col = VALUES(col) | ON CONFLICT DO UPDATE SET col = EXCLUDED.col |
REPLACE INTO | No direct equivalent (use ON CONFLICT DO UPDATE) |
Common Pitfalls
- INSERT IGNORE silences all errors, not just duplicates. Type mismatches, NOT NULL violations, and out-of-range values are also converted to warnings. Check
SHOW WARNINGSafter the statement to catch silent failures. - Auto-increment gaps. Both statements advance the auto-increment counter even when no row is inserted. This creates gaps in IDs. If sequential IDs matter to your application, investigate before using either.
- VALUES() is deprecated. In MySQL 8.0.20+, use the row alias syntax (
AS new_row ... ON DUPLICATE KEY UPDATE col = new_row.col) instead ofVALUES(col). - REPLACE INTO deletes the old row. This fires ON DELETE triggers, cascades foreign keys, and assigns a new auto-increment ID. It is almost never what you want.
- Lock contention.
ON DUPLICATE KEY UPDATEholds a write lock on the conflicting row during the update. Under very high concurrency, this can cause lock-wait timeouts. Consider batching or reducing conflict frequency. - Checking affected rows.
ROW_COUNT()returns 1 for insert, 2 for update, 0 for no-change. Application ORMs may interpret "2 rows affected" incorrectly. Test your ORM behavior.
Summary
- Use
INSERT IGNOREwhen duplicates can be safely discarded without modifying existing data. It is simpler and faster. - Use
INSERT ... ON DUPLICATE KEY UPDATEwhen you need to merge or upsert: keep existing rows but update them with new values. - Avoid
REPLACE INTOunless you specifically need delete-then-insert semantics and understand the foreign key implications. - In MySQL 8.0.20+, use the row alias syntax (
AS new_row) instead of the deprecatedVALUES()function. - In PostgreSQL, the equivalent is
ON CONFLICT DO NOTHING/ON CONFLICT DO UPDATE. - Always check
SHOW WARNINGSafterINSERT IGNOREto catch silently swallowed errors beyond just duplicate keys.
Related reading
- INSERT IGNORE vs INSERT ... ON DUPLICATE KEY UPDATE
- INSERT INTO ... SELECT FROM ... ON DUPLICATE KEY UPDATE
- Insert into a MySQL table or update if exists
- Insert into a MySQL table or update if exists
- Insert, on duplicate update in PostgreSQL?
- Insert to cassandra from python using cql
- INSERT with SELECT
- Inserting a Python datetime.datetime object into MySQL

System Design Fundamentals
Build a strong foundation in designing scalable, reliable distributed systems.
View the courseTrack what you have practised
A free account saves your progress, solutions and study plan across every problem on Codemia.
System Design practice on Codemia
Work through 120+ system design problems with detailed solutions, from rate limiters to multi-region storage.