3

次のようなバッチ挿入ステートメントがある場合:

INSERT INTO TABLE VALUES (x,y,z),(x2,y2,z2),(x3,y3,z3);

x2主キーに違反している場合、エラーは の処理の前または後にスローされx3ますか?

具体的には、次のように、Python と PyMySQL を使用して、try-catch ブロックに大量のバッチ挿入があります。

conn = myDB.cursor() 
try:
     conn.execute("INSERT INTO TABLE VALUES (x,y,z),(x2,y2,z2),(x3,y3,z3);")
except pymysql.Error as  msg:
     print("MYSQL ERROR!:{0}".format(msg)) #print error

バッチ挿入内のタプルの 1 つが失敗してエラーが出力された場合、同じバッチ内の残りのタプルがまだ処理されていることを確認したいと思います。

私の動機は、2 つのサーバー間で大量のデータを転送することです。サーバー 1 では、データはログ ファイルに保存され、サーバー 2 の MySQL に挿入されます。一部のデータはサーバー 2 の MySQL に既に存在するため、多くの障害が発生します。ただし、バッチ挿入を使用せずINSERT INTO、(数百万の) レコードごとに個別にすると、動作がはるかに遅くなるようです。したがって、どちらにしても問題があります。バッチ挿入を使用すると、重複した失敗によりステートメント全体が破壊され、バッチ挿入がないと、プロセスにはるかに長い時間がかかります。

4

2 に答える 2

4

MySQL が複数の挿入 (または更新) ステートメントを処理する方法は、テーブル エンジンとサーバー SQL モードによって異なります。

ここで質問している主要な制約にとって本当に重要なのはテーブル エンジンだけですが、全体像を理解することが重要であるため、時間をかけて詳細を追加します。お急ぎの場合は、以下の最初と最後のセクションだけをお読みください。

テーブル エンジン

MyISAM のような非トランザクション テーブル エンジンの場合、各挿入または更新が順次実行され、不良行が検出されてステートメントが中止されたときにロールバックできないため、簡単に部分更新を実行してしまう可能性があります。

ただし、InnoDB などのトランザクション テーブル エンジンを使用する場合、insert または update ステートメント中に制約違反が発生すると、ステートメントが中止されるだけでなく、その時点までに行われたすべての変更のロールバックがトリガーされます。

SQL モード

サーバー SQL モードは、キー制約に違反していないが、挿入または更新しようとしているデータが挿入先の列の定義に適合しない場合に重要になります。例えば:

  • NOT NULLすべての列に値を指定せずに行を挿入する
  • '123'(ではなく123)数値型で定義された列への挿入
  • CHAR(3)値を保持するための列の更新'four'

このような場合、厳格モードが有効な場合、MySQL はエラーをスローします。ただし、厳密モードが有効でない場合は、多くの場合、代わりに間違いを「修正」し、あらゆる種類の潜在的に有害な動作を引き起こす可能性があります (2 つの例については、MySQL の「切り捨てられた正しくない INTEGER 値」およびmysql 文字列変換が 0を返すを参照してください)。

危険だ、ウィル・ロビンソン!

非トランザクション テーブルと厳密モードには、潜在的な「落とし穴」がいくつかあります。どのテーブルエンジンを使用しているかは教えてくれませんでしたが、現在書かれているこの回答は明らかに非トランザクションテーブルを使用しており、それが結果にどのように影響するかを知ることが重要です.

たとえば、次の一連のステートメントについて考えてみます。

SET sql_mode = '';  # This will make sure strict mode is not in effect

CREATE TABLE tbl (
  id INT NOT NULL AUTO_INCREMENT PRIMARY KEY,
  val INT
) ENGINE=MyISAM;  # A nontransactional table engine (this used to be the default)

INSERT INTO tbl (val) VALUES (1), ('two'), (3);

INSERT INTO tbl (val) VALUES ('four'), (5), (6);

INSERT INTO tbl (val) VALUES ('7'), (8), (9);

厳密モードは有効ではないため、9 つの値すべてが挿入され、無効な文字列が整数に変換されることは驚くべきことではありません。サーバーは数値として認識できるほど賢いですが、またはを'7'認識しないため、MySQL の数値型のデフォルト値に変換されます。'two''four'

mysql> SELECT val FROM tbl;
+------+
| val  |
+------+
|    1 |
|    0 |
|    3 |
|    0 |
|    5 |
|    6 |
|    7 |
|    8 |
|    9 |
+------+
9 rows in set (0.00 sec)

でもう一度やってみてくださいsql_mode = 'STRICT_ALL_TABLES'。簡単に言うと、最初のINSERTステートメントでは部分的な挿入が行われ、2 番目のステートメントは完全に失敗し、3 番目のステートメントは暗黙のうちに強制'7'され7ます (私に言わせれば、これはあまり「厳密」ではないように見えますが、これは文書化された動作であり、それは不合理です)。

しかし、待ってください。で試してみてくださいsql_mode = 'STRICT_TRANS_TABLES'。最初のステートメントはエラーではなく警告をスローしますが、2 番目のステートメントはまだ失敗します。LOAD DATAこれは、多数のファイルを使用していて、失敗するファイルとそうでないファイルがある場合に特にイライラする可能性があります (クローズド バグ レポート を参照してください)。

何をすべきか

特にキー違反の場合、重要なのは、テーブル エンジンがトランザクション対応であるか (例: InnoDB)、そうでないか (例: MyISAM) だけです。トランザクション テーブルで作業している場合、質問の Python コードにより、MySQL サーバーは次の順序で処理を実行します。

  1. ステートメントを解析しINSERT、トランザクションを開始します。*
  2. 最初のタプルを挿入します。
  3. 2 番目のタプルを挿入します (キー制約に違反しています)。
  4. トランザクションをロールバックします。
  5. にエラー メッセージを送信しますpymysql

*トランザクションを開始する前にステートメントを解析することは理にかなっていますが、正確な実装がわからないため、これらを 1 つのステップとしてまとめます。

この場合、不正なタプルより前の変更は、スクリプトがサーバーからエラー メッセージを受信してexcept​​ブロックに入るまでに、すでに元に戻されています。

ただし、非トランザクション テーブルで作業している場合、テーブル エンジンはトランザクション ステートメントをサポートしていないため、サーバーはステップ 4 (およびステップ 1 の関連部分) をスキップします。この場合、スクリプトがブロックに入った時点で、最初のタプルが挿入され、2 番目のタプルが壊れており、正常にexcept挿入された行数を簡単に判断できない場合があります。最後の挿入または更新ステートメントがエラーをスローした場合は 1。

部分的な更新は絶対に避けてください。ステートメントが完全に成功するか完全に失敗するかを単純に確認するよりも、修正するのははるかに困難です。このタイプの状況では、ドキュメントは次のことを示唆しています

[部分的な更新] を回避するには、テーブルを変更せずに中止できる単一行ステートメントを使用します。

そして私の意見では、それはまさにあなたがすべきことです。Python でループを記述することはほとんど難しくなく、値をハードコーディングするのではなくパラメーターとして適切に値を挿入している限り、コードを繰り返す必要はありません。これは既に行っていますよね? 右???>:(

代替案

ときどき制約に違反することが予想され、挿入しようとする行がすでに存在することが判明したときに他のアクションを実行したい場合は、`INSERT ... ON DUPLICATE KEY UPDATE'に興味があるかもしれません。これにより、ものを数えるなどの計算体操の驚くべき偉業を実行できます。

mysql> create table counting_is_fun (
    -> stuff int primary key,
    -> ct int unsigned not null default 1
    -> );
Query OK, 0 rows affected (0.12 sec)

mysql> insert into counting_is_fun (stuff)
    -> values (1), (2), (5), (3), (3)
    -> on duplicate key update count = count + 1;
Query OK, 6 rows affected (0.04 sec)
Records: 5  Duplicates: 1  Warnings: 0

mysql> select * from counting_is_fun;
+-------+-------+
| stuff | count |
+-------+-------+
|     1 |     1 |
|     2 |     1 |
|     3 |     2 |
|     5 |     1 |
+-------+-------+
4 rows in set (0.00 sec)

(注: 挿入したタプルの数を、クエリによって "影響を受ける" 行の数と、後でテーブル内の行の数と比較します。数えるのは楽しいですか?)

または、現在挿入しているデータが現在テーブルにあるデータと少なくとも同じくらい良いと思われる場合は、調べることができますREPLACE INTOが、これは SQL 標準に対する MySQL 固有の拡張であり、いつものように、特に外部キー参照に関連付けられたAUTO_INCREMENTフィールドとアクションに関しては、癖があります。ON DELETE

人々が提案するのが大好きなもう 1 つのアプローチは、INSERT IGNORE. これはエラーを無視し、そのまま続行します。いいですよね?とにかく、誰がエラーを必要としますか? 解決策としてこれが気に入らない理由は次のとおりです。

私はINSERT IGNORE時々使用しますが、ドキュメンテーションのフラットアウトが何かを行うための「正しい方法」を教えてくれるときは、自分を裏切らないでください. 最初にその方法を試してください。間違った方法でデータの完全性を侵害し、すべてを永久に台無しにする危険性がある場合は、少なくとも十分な情報に基づいた決定を下したことになります。

于 2014-09-18T21:26:23.190 に答える