You run an import, a plugin update or a checkout, and MySQL answers with ERROR 1062 (23000): Duplicate entry '123' for key 'PRIMARY'. Half your data is in, half isn't, and the site is acting strange. It looks scary, but it's one of the more predictable database errors once you know what it's actually complaining about.
What error 1062 actually means
MySQL refuses to store two rows that share the same value in a column (or set of columns) marked PRIMARY or UNIQUE. The message names both the value and the key, so you already have the two clues you need: which value collided and which index rejected it.
Typical messages you'll see:
Duplicate entry '5' for key 'PRIMARY'– an ID collision, common after partial imports.Duplicate entry 'admin@example.com' for key 'user_email'– a unique email or username.Duplicate entry 'home' for key 'option_name'– a cpanel" class="auto-link">WordPresswp_optionsrow inserted twice.
The usual causes
| Situation | Why it happens |
|---|---|
| Importing a .sql file into a non-empty database | The dump's INSERT statements hit rows that already exist. |
| Re-running a failed or timed-out import | The first run inserted part of the data before dying. |
| AUTO_INCREMENT counter behind the real max ID | Rows were copied in manually, so the counter hands out an ID that's already taken. |
| Plugin or migration inserting the same option/row twice | A race condition or a double-fired activation hook. |
| Merging two databases or tables | Both sides used IDs starting at 1. |
Step 1: Read the message carefully
Note the table name (phpMyAdmin and WordPress's debug log both show it) and the key name. Then look at what's already there. In phpMyAdmin's SQL tab, or over SSH:
SELECT * FROM wp_options WHERE option_name = 'home';
SHOW CREATE TABLE wp_options;
SHOW CREATE TABLE lists every PRIMARY KEY and UNIQUE KEY, so you'll know exactly which constraint fired.
Step 2: Fix it based on the cause
Failed or repeated import
Take a backup first. Then, if the database should be a clean copy of the dump, empty it and import again. In cPanel's phpMyAdmin, select the database, tick Check all, choose Drop, and re-run the import. Over SSH:
mysql -u dbuser -p dbname < backup.sql
If you only want to add missing rows, regenerate the dump with --insert-ignore or use INSERT IGNORE so duplicates are skipped instead of aborting the import. Be aware that skipping means the existing row wins, so only do this when you're sure the old rows are correct.
AUTO_INCREMENT behind the data
Check the highest ID and the counter:
SELECT MAX(id) FROM mytable;
SHOW TABLE STATUS LIKE 'mytable';
If the Auto_increment value is lower than MAX(id)+1, bump it:
ALTER TABLE mytable AUTO_INCREMENT = 5001;
Use a number comfortably above the current maximum. InnoDB also resets the counter from the table's maximum on restart in older MySQL versions, so if it keeps drifting, look for whatever is inserting explicit IDs.
Intentional upserts
If your code is meant to update existing rows, don't fight the error. Use an upsert:
INSERT INTO settings (name, value)
VALUES ('theme', 'blue')
ON DUPLICATE KEY UPDATE value = VALUES(value);
Avoid REPLACE INTO unless you understand it deletes and re-inserts the row, which can break foreign keys and change IDs.
WordPress and WooCommerce
For WordPress, the error often shows in wp-content/debug.log as "WordPress database error Duplicate entry...". Find the table in the log line. For wp_options or wp_postmeta, deactivate the plugin named in the stack trace and see whether it stops. If a plugin created a duplicate row, delete the extra one, keeping the row with the correct value:
DELETE FROM wp_options WHERE option_id = 4821;
Always confirm the option_id with a SELECT first. Don't run a delete you haven't previewed.
Merging two tables
Offset one side's IDs before merging, or let MySQL generate new ones by leaving the ID column out of the insert:
INSERT INTO orders (customer, total)
SELECT customer, total FROM orders_old;
Remember to remap any child tables that reference the old IDs.
Step 3: Verify
- Re-run the failing action and confirm the error is gone.
- Compare row counts between source and destination:
SELECT COUNT(*) FROM tablename; - Check the site's key pages, then watch
debug.logor the MySQL error log for a day.
Prevention
- Import into an empty database, not on top of live data.
- Take a backup before every import so you can roll back cleanly.
- For big dumps, use SSH or
BigDump-style splitting rather than the browser, so imports don't time out half-way. See our guide on phpMyAdmin import errors for large SQL files. - Let MySQL assign IDs. Hard-coding them is the main reason counters fall behind.
- Keep plugins updated; duplicate-row bugs are often fixed in later releases.
Frequently Asked Questions
Is error 1062 a sign my database is corrupted?
No. It's a constraint doing its job. Corruption shows up as "table is marked as crashed" or InnoDB errors, which are a different problem.
Can I just remove the unique key to make the error go away?
You can, but you shouldn't. The key usually protects something real, like unique logins or option names. Removing it hides the problem and lets bad data in.
Why did my import stop halfway?
MySQL aborts on the first failing statement unless you tell it otherwise. Everything before that statement is already saved, so re-running without cleaning up causes this exact error.
Should I use INSERT IGNORE or ON DUPLICATE KEY UPDATE?
Use INSERT IGNORE to keep existing rows untouched. Use ON DUPLICATE KEY UPDATE when the new data should overwrite the old.
If you're stuck on a stubborn 1062 and aren't comfortable running queries on a live database, open a ticket with SkyServer support and tell us the table and key name from the error. We'll take a look.
