すべてを1つの大きなクエリにカプセル化する必要がないのは、実際には何も解決されないためです。
必要なのは、行のロック、または新しい行が挿入されるインデックスのロックです。
では、どうすれば排他ロックを取得できますか?
2つの接続、mysql1とmysql2。それぞれがSELECT ... FOR UPDATE
を使用して排他ロックを要求します。 。テーブル'history'には、インデックスが付けられた列'user_id'があります。 (これは外部キーでもあります。)行が見つからないため、どちらも異常が発生しないかのように正常に進行しているように見えます。 user_id 2808は有効ですが、履歴には何もありません。
mysql1> start transaction;
Query OK, 0 rows affected (0.00 sec)
mysql2> start transaction;
Query OK, 0 rows affected (0.00 sec)
mysql1> select * from history where user_id = 2808 for update;
Empty set (0.00 sec)
mysql2> select * from history where user_id = 2808 for update;
Empty set (0.00 sec)
mysql1> insert into history(user_id) values (2808);
...プロンプトが返されません...応答がありません...別のセッションにもロックがあるため...しかし:
mysql2> insert into history(user_id) values (2808);
ERROR 1213 (40001): Deadlock found when trying to get lock; try restarting transaction
次に、mysql1は挿入時にすぐに成功を返します。
Query OK, 1 row affected (3.96 sec)
残っているのは、mysql1がCOMMIT
することだけです。 そして魔法のように、0エントリのユーザーが複数のエントリを挿入するのを防ぎました。デッドロックが発生したのは、両方のセッションで互換性のないことが発生する必要があるためです。mysql1は、コミットする前にmysql2がロックを解放する必要があり、mysql2は、挿入できるようになる前にmysql1がロックを解放する必要がありました。誰かがその戦いに負ける必要があり、一般的に最も少ない仕事をしたスレッドが敗者です。
しかし、SELECT ... FOR UPDATE
を実行したときに、すでに1つ以上の行が存在していた場合はどうなりますか。 ?その場合、ロックは行にあるため、SELECT
を試行する2番目のセッション SELECT
の待機を実際にブロックします 最初のセッションがCOMMIT
のいずれかを決定するまで またはROLLBACK
、その時点で、2番目のセッションは行数の正確なカウント(最初のセッションによって挿入または削除されたものを含む)を確認し、ユーザーがすでに最大許容数を持っていると正確に判断できたはずです。
競合状態を超えることはできませんが、それらをロックアウトすることはできます。