2013-06-12 6 views
7

データベースにユーザー名が存在するかどうかを検索して、そのユーザー名をSELECTINSERTステートメントの間に傍受せずに新しい行としてデータベースに挿入することはできますか?まだ存在しないInnoDB行をロックするにはどうしたらいいですか?

ほとんどの場合、存在しない行をロックしているように見えます。存在しない行をユーザー名"Foo"でロックして、データベースに存在するかどうかを確認し、中断することなくデータベースに挿入するようにします。

LOCK IN SHARE MODEFOR UPDATEを使用していますが、私が知っている限り、それはすでに存在する行でのみ動作することがわかります。私はこの状況で何をすべきか分かりません。

答えて

9

(ケースであるべきで、そうでない場合、いずれかを追加し、pererrably UNIQUEもの)usernameにインデックスがある場合、SELECT * FROM user_table WHERE username = 'foo' FOR UPDATE;を発行すると、このユーザの作成から任意の同時トランザクションの独立を防ぐ(ならびに「う非ユニークなインデックスの場合は「前の」および「次の」可能な値)。

適切なインデックスが見つかった場合(WHERE条件を満たすため)、効率的なレコードロックが不可能になり、テーブル全体がロックされます*。

このロックは、SELECT ... FOR UPDATEを発行したトランザクションの終了時まで保持されます。

このトピックに関するいくつかの非常に興味深い情報はthese manual pagesにあります。

* ため、私は実際a record lock is actually a lock on index recordsで効率的を言います。適切なインデックスが見つからない場合、デフォルトのclustered indexのみが使用でき、完全にロックされます。

+0

インデックスを持たないものに対して新しい行を追加するとどうなりますか?それはインデックスなしでテーブル全体をロックしますか? – xLite

+1

はい、私はそれに言及するのを忘れました。レコード・ロックに適切な索引が見つからない場合は、表全体が読み取り専用になります。 – RandomSeed

+0

'username'カラムに' UNIQUE'インデックスがなく、 'user_id'カラムに' PRIMARY'がある場合はどうなりますか? 'FOR UPDATE'で検索すると、ユーザー名 'foo'がロックされていることが確認できますか?私が完全に気付いていないかもしれない落とし穴がありますか?例えば。 'user_id'と' username'の両方が同時にインデックスを持っているなら( 'PRIMARY'と' UNIQUE')、ロックが不可能でエラーや何かが出るでしょうか?私はこれについてまだかなり謝っています。それはややばかな質問です。返信いただきありがとうございます! – xLite

7

上記の答えは、SELECT ... FOR UPDATEは同時セッション/トランザクションが同じレコードを挿入することを防止しますが、完全な真実ではありません。私は現在同じ問題を抱えていますが、SELECT ... FOR UPDATEがそのような状況ではほとんど役に立たないという結論に達しました。

同時トランザクション/セッションでもSELECT ... FOR UPDATEと同じレコード/インデックス値を使用すると、MySQLは即座に即座に(非ブロッキングで)エラーをスローすることなく受け入れます。もちろん、他のセッションがそれをしたらすぐに、あなたのセッションもレコードを挿入することはできません。あなたや他のセッション/トランザクションは状況に関する情報を得ておらず、実際にそうしようとするまでレコードを安全に挿入できると思います。状況に応じて、挿入しようとすると、デッドロックまたは重複キーエラーが発生します。

つまり、SELECT ... FOR UPDATEは、SELECT ... FOR UPDATEを実行してもそれぞれのレコードが他のセッションに挿入されるのを防ぎますが、それぞれのレコードが見つからない場合は、実際にそのレコードを挿入することはできません。 IMHOは、「最初のクエリを挿入して」メソッドを役に立たなくします。

この問題の原因は、MySQLには、には実際にはのレコードをロックする方法がありません。 2つの同時セッション/トランザクションでは、存在しないレコード "FOR UPDATE"を同時にロックすることができます。これは実際には不可能であり、開発を大幅に困難にします。

これを回避する唯一の方法は、セマフォー・テーブルを使用するか、挿入するときにテーブル全体をロックすることです。テーブル全体のロックやセマフォーテーブルの使用に関する詳細は、MySQLのマニュアルを参照してください。

ちょうど私の2セント...

+1

別のオプションは、すべての状況で必ずしも理想的ではありませんが、SELECT ... FOR UPDATEをスキップしてINSERTを実行し、結果の重複キーエラーを処理します(これは、挿入が最初の操作実行される)。しかし、パフォーマンスのペナルティがあると確信していますが、多くの場合、実行される他の操作と比較して無視できるものであり、ミューテックス・テーブルを作成する手間を省くことができます。 – Sanuden

+0

@サウデン私のアプリケーションでは、データベースからエラーを得ることは、常にデータベースや間違った(SQL)コードで問題があることを意味しますが、*データそのものには問題がないことを意味します*。これは私が長い間前に重大な理由から行ったアーキテクチャ上の決定です。たとえば、自分のやり方では、データベースから返ってきたエラー番号をチェックして、それぞれの場所でどのようなエラーが発生しているのかを確認する必要があります。エラー番号は変わらず、移植は困難です。 – Binarus

+0

"INSERT ... ON DUPLICATE KEY UPDATE"は一貫して 'X'ロックを生成します。これは、2つのプロセスがトランザクションに入るのを防ぐために使用できます。 https://stackoverflow.com/a/48453879/710358 – M1L0U

4

MySQLでは存在しないレコードをロックすることはできません。それについてのいくつかのバグレポートがあります。

回避策の一つは、新しいレコードが挿入される前に、既存のレコードがロックされますmutex tableを、使用することです。たとえば、売り手と商品の2つのテーブルがあります。売り手には多くの商品がありますが、重複商品はありません。この場合、売り手テーブルはミューテックステーブルとして使用できます。新しい商品が挿入される前に、売り手の記録にロックが作成されます。この追加のクエリでは、常に1つのスレッドしかアクションを実行できないことが保証されています。重複はありません。デッドロックはありません。

関連する問題