MySQLではなぜINSERT IGNORE INTO
外部キー制約エラーを警告に変更しませんか?
テーブルにいくつかのレコードを挿入しようとしていますが、MySQLはエラーの原因となるレコードやエラーを除外し、残りを挿入することを期待しています。誰か提案はありますか?
そしてその SET FOREIGN_KEY_CHECKS = 0;
は私の答えではありません。制約を無視する行がまったく挿入されないことを期待しているからです。
ありがとう
[新しい回答]
これを提起してくれた@NeverEndingQueueに感謝します。 MySQLがついにこの問題を修正したようです。この問題が最初に修正されたバージョンはわかりませんが、現在、次のバージョンでテストしましたが、問題はもうありません。
mysql> SHOW VARIABLES LIKE "%version%";
+-------------------------+------------------------------+
| Variable_name | Value |
+-------------------------+------------------------------+
| innodb_version | 5.7.22 |
| protocol_version | 10 |
| slave_type_conversions | |
| tls_version | TLSv1,TLSv1.1 |
| version | 5.7.22 |
| version_comment | MySQL Community Server (GPL) |
| version_compile_machine | x86_64 |
| version_compile_os | Linux |
+-------------------------+------------------------------+
明確にするために:
mysql>子にIGNOREを挿入 ->値 ->(NULL、1) ->、(NULL、2) - >、(NULL、3) ->、(NULL、4) ->、(NULL、5) ->、(NULL、6); クエリOK、 影響を受ける4行、2つの警告 (0.03秒) レコード:6重複:2警告:2
この最後のクエリの意味を理解し、問題が修正された理由を理解するには、以下の古い回答に進んでください。
[古い回答]
私の解決策は問題の回避策であり、実際の解決策は常にMySQL自体の中で問題を修正することです。
次の手順で問題が解決しました:
a。次のテーブルとデータを用意することを検討してください。
mysql>
CREATE TABLE parent (id INT AUTO_INCREMENT NOT NULL
, PRIMARY KEY (id)
) ENGINE=INNODB;
mysql>
CREATE TABLE child (id INT AUTO_INCREMENT
, parent_id INT
, INDEX par_ind (parent_id)
, PRIMARY KEY (id)
, FOREIGN KEY (parent_id) REFERENCES parent(id)
ON DELETE CASCADE
ON UPDATE CASCADE
) ENGINE=INNODB;
mysql>
INSERT INTO parent
VALUES (NULL), (NULL), (NULL), (NULL), (NULL), (NULL);
mysql>
SELECT * FROM parent;
+----+
| id |
+----+
| 1 |
| 2 |
| 3 |
| 4 |
| 5 |
| 6 |
+----+
b。ここで、問題を示すために一部の行を削除する必要があります。
mysql>
DELETE FROM parent WHERE id IN (3, 5);
c。問題:次の子行を挿入しようとすると、問題が発生します。
mysql>
INSERT IGNORE INTO child
VALUES
(NULL, 1)
, (NULL, 2)
, (NULL, 3)
, (NULL, 4)
, (NULL, 5)
, (NULL, 6);
ERROR 1452 (23000): Cannot add or update a child row: a foreign key constraint f
ails (`test`.`child`, CONSTRAINT `child_ibfk_1` FOREIGN KEY (`parent_id`) REFERE
NCES `parent` (`id`) ON DELETE CASCADE ON UPDATE CASCADE)
mysql>
SELECT * FROM child;
Empty set (0.00 sec)
IGNORE
キーワードが使用されていても、生成されたエラーが(想定どおり)警告に変換されないため、MySQLは要求された操作をキャンセルします。問題が明らかになったところで、エラーに直面することなく、最後のinsert intoステートメントをどのように実行できるかを見てみましょう。
d。SOLUTION:挿入されたレコードにも番号にも依存しない他の定数ステートメントによって、insert intoステートメントをラップします。
mysql>
SET FOREIGN_KEY_CHECKS = 0;
mysql>
INSERT INTO child
VALUES
(NULL, 1)
, (NULL, 2)
, (NULL, 3)
, (NULL, 4)
, (NULL, 5)
, (NULL, 6);
mysql>
DELETE FROM child WHERE parent_id NOT IN (SELECT id FROM parent);
mysql>
SET FOREIGN_KEY_CHECKS = 1;
これは最適ではないことはわかっていますが、MySQLが問題を修正していない限り、これは私が知っている最良の方法です。特に、PHPでmysqliライブラリを使用すると、すべてのステートメントを1つの要求で実行できるためです。
問題に対する最も単純なmysqlの解決策は、制約を確認するために外部キーテーブルに参加することだと思います。
INSERT INTO child (parent_id, value2, value3)
SELECT p.id, @new_value2, @new_value3
FROM parent p WHERE p.id = @parent_id
しかし、不足している外部キーに基づいてレコードを破棄するのは奇妙に思えます。たとえば、外部キーのチェックでINSERT IGNOREエラーが発生することを好みます。
私は信じている INSERT IGNORE
は、ストレージエンジンレイヤーではなく、サーバーレイヤーからのエラーを無視することを目的としています。したがって、重複キーエラー(主な使用例)と特定のデータ変換には役立ちますが、ストレージエンジンレイヤーに起因する外部キーエラーには役立ちません。
特定の要件については:
テーブルに多数のレコードを挿入しようとしていますが、MySQLはエラーを生成するレコードやエラーを除外し、残りを挿入することを期待しています。誰か提案はありますか?
そのため、mysql -f
を使用すると、エラーが発生しても実行を継続できます。たとえば、次のようなファイルがあるとします。
insert into child (parent_id, ...) values (bad_parent_id, ...);
insert into child (parent_id, ...) values (good_parent_id, ...);
次に、そのファイルを次のようにロードできます。これにより、適切な行が挿入され、不良行のエラーが無視されます。
mysql -f < inserts.sql
この問題はMySQL 5.7で修正されたようです。 https://bugs.mysql.com/bug.php?id=7885 を参照してください。
今度は外部キー制約エラーが警告に変わります。
この問題は5.1、5.5、5.6ビルドに存在しますが、5.7でいくつかの改善が見られ、WL#6614の後でエラーがエラーではなく警告に変換されています。
INSERT IGNOREは、遅延の回避策にすぎません。最初に重複レコードを挿入するべきではありません。さらに、プライマリ/ユニークは外部キーと同じではありません。また、IGNOREは他のエラーや警告(ゼロによる除算、データの切り捨て)も無視することを覚えておいてください。通常、これは良いことではありません。
この場合、ほとんどの場合、INSERT IGNOREの代わりにREPLACEを使用する方が理にかなっています。別のオプションはON DUPLICATE KEY UPDATEです。
あなたの場合、挿入前に欠落している親を削除できない場合は、次のような一時テーブルで同じことを管理できます。
create temporary table tmpTable (col1, col2, ...);
insert into tmpTable values (row1), (row2), ...; -- same insert you already had
alter table tmpTable add key (foreignColumnName); -- only if # of rows is big
insert into table select null /* AI id */, col1, col2 ...
from tmpTable join parentTable on foreignColumnName = parent.id;
よろしく、ホセ。