できない.dev

MySQL で「Duplicate entry」が出て INSERT できない

Duplicate entry は PRIMARY KEY か UNIQUE 制約に同じ値を入れようとしたときのエラーである。
エラー文の末尾に出るキー名から、どの制約に当たったかを特定するのが先決。
重複を許すのか、更新に倒すのか、無視するのかで対処が変わる。

公開: 更新:

実行例あり(2026-09-18 に実環境で検証)

要約

ERROR 1062 (23000): Duplicate entry 'alice@example.com' for key 'users.email_unique' は、一意性制約に同じ値を入れようとしたときのエラーである。
読むべきは for key の後ろに出るキー名で、これがどの制約に当たったかを直接示している。

mysql> INSERT INTO users (email) VALUES ('alice@example.com');
ERROR 1062 (23000): Duplicate entry 'alice@example.com' for key 'users.email_unique'

キー名が PRIMARY なら主キー、それ以外なら同名のユニークインデックスである。
まずここを確定させてから、重複をどう扱うかを決める。

実行例

email_unique を持つ users に同じ alice@example.com を入れると ERROR 1062 (23000) が for key 'users.email_unique' 付きで出て、大文字だけが違う Alice@example.com も照合順序 utf8mb4_0900_ai_ci のため同じキーで弾かれている。
行エイリアス形の ON DUPLICATE KEY UPDATE は警告なしに ROW_COUNT() が 2 を返して既存行の name だけを更新する一方、同じ処理を VALUES(name) で書くと Warning (Code 1287) が付き、INSERT IGNORE ではエラーが 1062 の警告に落ちた。

$ mysql -u root --force -v demo < bad.sql
--------------
INSERT INTO users (email) VALUES ('alice@example.com')
--------------
 
ERROR 1062 (23000) at line 1: Duplicate entry 'alice@example.com' for key 'users.email_unique'
--------------
INSERT INTO users (email) VALUES ('Alice@example.com')
--------------
 
ERROR 1062 (23000) at line 2: Duplicate entry 'Alice@example.com' for key 'users.email_unique'
$ mysql -u root demo < show.sql
*************************** 1. row ***************************
       Table: users
Create Table: CREATE TABLE `users` (
  `id` int NOT NULL AUTO_INCREMENT,
  `email` varchar(255) NOT NULL,
  `name` varchar(50) DEFAULT NULL,
  PRIMARY KEY (`id`),
  UNIQUE KEY `email_unique` (`email`)
) ENGINE=InnoDB AUTO_INCREMENT=4 DEFAULT CHARSET=utf8mb4 COLLATE=utf8mb4_0900_ai_ci
$ mysql -u root -t -v --show-warnings demo < solve_upsert.sql
--------------
INSERT INTO users (email, name) VALUES ('alice@example.com', 'Alice') AS new
  ON DUPLICATE KEY UPDATE name = new.name
--------------
 
--------------
SELECT ROW_COUNT()
--------------
 
+-------------+
| ROW_COUNT() |
+-------------+
|           2 |
+-------------+
--------------
SELECT * FROM users
--------------
 
+----+-------------------+-------+
| id | email             | name  |
+----+-------------------+-------+
|  1 | alice@example.com | Alice |
+----+-------------------+-------+
$ mysql -u root -t -v --show-warnings demo < legacy_upsert.sql
--------------
INSERT INTO users (email, name) VALUES ('alice@example.com', 'ALICE')
  ON DUPLICATE KEY UPDATE name = VALUES(name)
--------------
 
Warning (Code 1287): 'VALUES function' is deprecated and will be removed in a future release. Please use an alias (INSERT INTO ... VALUES (...) AS alias) and replace VALUES(col) in the ON DUPLICATE KEY UPDATE clause with alias.col instead
$ mysql -u root -t -v demo < solve_ignore.sql
--------------
INSERT IGNORE INTO users (email) VALUES ('alice@example.com')
--------------
 
--------------
SHOW WARNINGS
--------------
 
+---------+------+------------------------------------------------------------------+
| Level   | Code | Message                                                          |
+---------+------+------------------------------------------------------------------+
| Warning | 1062 | Duplicate entry 'alice@example.com' for key 'users.email_unique' |
+---------+------+------------------------------------------------------------------+

— 2026-09-18 時点の出力

検証環境

検証日
実行環境
mysql:8.4Oracle Linux Server 9.8

この記事の「実行例」は、上記の環境で実際にコマンドを実行して得られた出力をそのまま掲載しています。 再現手順はリポジトリの検証スクリプトとして管理し、定期的に再実行して出力を更新しています。

よくある原因

  1. 単純な重複: すでにある値を入れようとしている。
    アプリ側で存在チェックをしてから INSERT していても、チェックと INSERT の間に別のリクエストが入れば起きる。
  2. AUTO_INCREMENT のずれ: id を明示して INSERT したり、ダンプを流し込んだりすると、次の採番値がテーブルの最大値より小さいままになる。
    以降の INSERT が既存 id とぶつかる。
  3. 複合ユニークキーの見落とし: UNIQUE (tenant_id, email) のような制約では、email だけを見ても重複判定はできない。
    組み合わせ全体で一意である必要がある。
  4. 照合順序による同一視: utf8mb4_0900_ai_ci のような大文字小文字を区別しない照合順序では、Alice@example.com と alice@example.com は同じ値として扱われる。
  5. 二重実行: ジョブのリトライやリクエストの二重送信で、同じ行を 2 回入れている。

解決策

1. どのキーに当たったかを特定する

SHOW CREATE TABLE users\G

出力の UNIQUE KEY / PRIMARY KEY の定義と、エラー文のキー名を突き合わせる。
複合キーなら構成列をすべて確認する。

2. 既存行の更新に倒す

INSERT INTO users (email, name) VALUES ('alice@example.com', 'Alice') AS new
  ON DUPLICATE KEY UPDATE name = new.name;

公式ドキュメント(新しいタブで開く) のとおり、PRIMARY KEY か UNIQUE インデックスの重複が起きたときに UPDATE へ切り替わる。
影響行数は新規挿入なら 1、更新なら 2 が返る。

挿入しようとした値は、VALUES の後ろに付けた行エイリアス(AS new)で new.name のように参照する。
行エイリアスは MySQL 8.0.19 から使える。name = VALUES(name) と書く古い形も 8.4 ではまだ動くが、8.0.20 から非推奨で、実行すると警告 1287('VALUES function' is deprecated)が出る。

3. 重複を捨ててよい場合

INSERT IGNORE INTO users (email) VALUES ('alice@example.com');

IGNORE は重複エラーを警告に落として処理を続ける。
ただし重複以外のエラーも警告に変わるため、取り込みバッチのような用途に限定する。

4. AUTO_INCREMENT を実データに合わせる

SELECT MAX(id) FROM users;
ALTER TABLE users AUTO_INCREMENT = 1001;

ダンプを取り込んだ直後に一度実行しておくと、以降の採番衝突を防げる。

5. 競合そのものを前提にする

存在チェックしてから INSERT する方式は競合状態を防げない。
一意性制約を残したまま、Duplicate entry を捕まえて「既に登録済み」として扱う実装にする方が安全である。

この記事は役立ちましたか?