web-dev-qa-db-ja.com

"INSERT IGNORE" vs "INSERT ... ON DUPLICATE KEY UPDATE"

多数の行を含むINSERTステートメントを実行している間、そうでなければ失敗の原因となる重複エントリーをスキップしたいのです。いくつかの調査の結果、私の選択肢は次のいずれかを使用することのようです。

  • いくらかの費用で不要な更新を意味するON DUPLICATE KEY UPDATE
  • INSERT IGNOREは、他の種類の失敗が予告なしにずれることを招くことを意味します。

私はこれらの仮定に正しいですか?重複の原因となる可能性がある行を単純にスキップし、他の行に進むための最善の方法は何ですか?

799
Thomas G Henry

私はINSERT...ON DUPLICATE KEY UPDATEを使うことを勧めます。

INSERT IGNOREを使用した場合、重複キーになると実際には行は挿入されません。しかし、ステートメントはエラーを生成しません。代わりに警告を生成します。これらのケースは次のとおりです。

  • PRIMARY KEYまたはUNIQUE制約を持つ列に重複キーを挿入します。
  • NOT NULL制約を使用して列にNULLを挿入します。
  • パーティション表に行を挿入しても、挿入した値はパーティションにマップされません。

REPLACEを使用する場合、MySQLは実際にはDELETEに続いてINSERTを内部的に実行します。これは、予期しない副作用をもたらします。

  • 新しい自動インクリメントIDが割り当てられます。
  • 外部キーを持つ従属行が削除されたり(カスケード外部キーを使用している場合)、そうでなければREPLACEを防ぐことができます。
  • DELETEを起動するトリガは不必要に実行されます。
  • 副作用はレプリケーションスレーブにも伝播されます。

修正: REPLACEINSERT...ON DUPLICATE KEY UPDATEの両方はMySQLに特有の非標準的な、独自の発明です。 ANSI SQL 2003では、同じニーズ(およびそれ以上)を解決できるMERGEステートメントが定義されていますが、MySQLはMERGEステートメントをサポートしていません。


ユーザーがこの投稿を編集しようとしました(編集はモデレーターによって却下されました)。編集はINSERT...ON DUPLICATE KEY UPDATEが新しい自動インクリメントIDを割り当てられるという主張を追加しようとしました。新しいIDは generated ですが、変更された行では使用されません。

下記のデモを参照してください。PerconaServer 5.5.28でテスト済み。設定変数innodb_autoinc_lock_mode=1(デフォルト):

mysql> create table foo (id serial primary key, u int, unique key (u));
mysql> insert into foo (u) values (10);
mysql> select * from foo;
+----+------+
| id | u    |
+----+------+
|  1 |   10 |
+----+------+

mysql> show create table foo\G
CREATE TABLE `foo` (
  `id` bigint(20) unsigned NOT NULL AUTO_INCREMENT,
  `u` int(11) DEFAULT NULL,
  PRIMARY KEY (`id`),
  UNIQUE KEY `u` (`u`)
) ENGINE=InnoDB AUTO_INCREMENT=2 DEFAULT CHARSET=latin1

mysql> insert into foo (u) values (10) on duplicate key update u = 20;
mysql> select * from foo;
+----+------+
| id | u    |
+----+------+
|  1 |   20 |
+----+------+

mysql> show create table foo\G
CREATE TABLE `foo` (
  `id` bigint(20) unsigned NOT NULL AUTO_INCREMENT,
  `u` int(11) DEFAULT NULL,
  PRIMARY KEY (`id`),
  UNIQUE KEY `u` (`u`)
) ENGINE=InnoDB AUTO_INCREMENT=3 DEFAULT CHARSET=latin1

上記は、IODKUステートメントが重複を検出し、更新を呼び出してuの値を変更することを示しています。 AUTO_INCREMENT=3は、idが生成されたが行では使用されていないことを示しています。

REPLACEは元の行を削除して新しい行を挿入し、を生成して新しい自動インクリメントIDを格納します。

mysql> select * from foo;
+----+------+
| id | u    |
+----+------+
|  1 |   20 |
+----+------+
mysql> replace into foo (u) values (20);
mysql> select * from foo;
+----+------+
| id | u    |
+----+------+
|  3 |   20 |
+----+------+
954
Bill Karwin

これが何を意味するのかを知りたい場合は、次のようにしてください。

CREATE TABLE `users_partners` (
  `uid` int(11) NOT NULL DEFAULT '0',
  `pid` int(11) NOT NULL DEFAULT '0',
  PRIMARY KEY (`uid`,`pid`),
  KEY `partner_user` (`pid`,`uid`)
) ENGINE=MyISAM DEFAULT CHARSET=utf8

主キーは、このクイックリファレンステーブルの両方の列に基づいています。主キーには一意の値が必要です。

さぁ、始めよう:

INSERT INTO users_partners (uid,pid) VALUES (1,1);
...1 row(s) affected

INSERT INTO users_partners (uid,pid) VALUES (1,1);
...Error Code : 1062
...Duplicate entry '1-1' for key 'PRIMARY'

INSERT IGNORE INTO users_partners (uid,pid) VALUES (1,1);
...0 row(s) affected

INSERT INTO users_partners (uid,pid) VALUES (1,1) ON DUPLICATE KEY UPDATE uid=uid
...0 row(s) affected

注意してください、上記は列をそれ自身に等しく設定することによってあまりに多くの余分な作業を節約しました、実際に更新は必要ありません。

REPLACE INTO users_partners (uid,pid) VALUES (1,1)
...2 row(s) affected

そして今、いくつかの複数行のテスト:

INSERT INTO users_partners (uid,pid) VALUES (1,1),(1,2),(1,3),(1,4)
...Error Code : 1062
...Duplicate entry '1-1' for key 'PRIMARY'

INSERT IGNORE INTO users_partners (uid,pid) VALUES (1,1),(1,2),(1,3),(1,4)
...3 row(s) affected

他のメッセージはコンソールで生成されず、テーブルデータにそれらの4つの値があります。同じ競技場からテストできるように、(1,1)以外のすべてを削除しました

INSERT INTO users_partners (uid,pid) VALUES (1,1),(1,2),(1,3),(1,4) ON DUPLICATE KEY UPDATE uid=uid
...3 row(s) affected

REPLACE INTO users_partners (uid,pid) VALUES (1,1),(1,2),(1,3),(1,4)
...5 row(s) affected

それであなたはそれを持っています。これはすべて、ほとんどデータがなく、本番環境ではない新しいテーブルで実行されたため、実行時間は微視的で無関係でした。現実のデータを持っている人は誰でもそれを提供することを歓迎します。

166
Paulus Maximus

追加するべき重要なこと:INSERT IGNOREを使用していて、あなたが主な違反をしている時、MySQLは警告を発しません!

たとえば、一度に100個のレコードを1つの不良レコードとともに挿入しようとすると、対話モードになります。

Query OK, 99 rows affected (0.04 sec)

Records: 100 Duplicates: 1 Warnings: 0

ご覧のとおり、警告はありません。この振る舞いは公式のMysqlドキュメンテーションでさえ間違って説明されています。

スクリプトに通知する必要がある場合、(キーの違反のために)いくつかのレコードが追加されていない場合は、mysql_info()を呼び出して "Duplicates"値について解析する必要があります。

39
Jens

私は日常的にINSERT IGNOREを使っています、そしてそれはまさにあなたが探している種類のふるまいのように聞こえます。インデックスの競合を引き起こす可能性のある行が挿入されず、それに応じてプログラムを計画していることを知っていれば、問題は発生しません。

18
David Z

私はこれが古いことを知っています、しかし私はINSERT..IGNOREで情報を見つけようとしている間に他の誰か(私のような)がこのページに到着した場合に備えてこのメモを追加します。

前述のように、INSERT..IGNOREを使用すると、INSERTステートメントの実行中に発生したエラーは代わりに警告として扱われます。

明示的に言及されていないことの1つは、挿入時に無効な値が最も近い値に調整されるということです(IGNOREキーワードが使用されていない場合、無効な値によってクエリが中止されます)。

17
Chris

Replace Intoはオプションのようです。またはで確認できます

IF NOT EXISTS(QUERY) Then INSERT

挿入または削除してから挿入します。私は最初にIF NOT EXISTSチェックをする傾向があります。

8
IEnumerator

ON DUPLICATE KEY UPDATEは標準では 本当に ではありません。 REPLACEと同じくらい標準的です。 SQL MERGE を参照してください。

基本的に両方のコマンドは標準コマンドの代替構文バージョンです。

8
Chris KL

IGNOREを挿入することの潜在的な危険性。 VARCHAR値をもっと長く挿入しようとしているのであれば、columnはで定義されています - 値は切り捨てられ、厳密モードが有効な場合は挿入されます。

3
LOL

クエリセットの最後にinsert ignoreステートメントを持つSHOW WARNINGS;を使用すると、重複したIDを含むすべての警告を含むテーブルが表示されます。

2
Ray Foss

テーブルに挿入し、主キーまたは一意のインデックスの競合がある場合は、その行を挿入する代わりに競合する行を更新します。

構文:

insert into table1 set column1 = a, column2 = b on duplicate update column2 = c;

ここで、このinsertステートメントはあなたが以前見たものとは異なって見えるかもしれません。この挿入ステートメントは、aとbの値を持つ行をtable1内の列column1とcolumn2にそれぞれ挿入しようとしています。

この文を深く理解しましょう。

たとえば、ここでcolumn1はtable1の主キーとして定義されています。

Table1の場合、column1に値 "a"を持つ行がないとします。そのため、このステートメントはtable1に行を挿入します。

Table1にcolumn2に値 "a"を持つ行があるとします。そのため、このステートメントは行のcolumn2の値を "c"で更新します。ここで、column1の値は "a"です。

そのため、新しい行を挿入したい場合は、そうでなければ主キーまたは一意のインデックスの競合でその行を更新してください。
このリンクについてもっと読む

2
user2613580