以下は、MySQL 5.6のトランザクションでInnoDBテーブルを使用してすべてのステートメントが実行されることを前提としています。また、col1とcol2が一緒になって一意のキーを形成します。FOR UPDATEを伴うMySQL読み取りロック
セッション1でSELECT * FROM table WHERE col1 = 1 AND col2 = 1 FOR UPDATE
を実行すると、その行のデータとその行の排他ロックが取得されます。したがって、セッション2で同じステートメントを実行すると、何かをする前にロックが解除されるか、タイムアウトになるまで待機します。ここまでは順調ですね。
-- Session 1
mysql> START TRANSACTION;
Query OK, 0 rows affected (0.00 sec)
mysql> SELECT * FROM `table` WHERE `col1` = 1 AND `col2` = 1 FOR UPDATE;
Empty set (0.01 sec)
-- Do whatever else takes some time
-- Session 2
mysql> START TRANSACTION;
Query OK, 0 rows affected (0.00 sec)
mysql> INSERT INTO `table` SET `col1` = 1, `col2` = 1;
ERROR 1205 (HY000): Lock wait timeout exceeded; try restarting transaction
私はそれを期待するとして、この動作は次のとおりです。
今SELECT ... FOR UPDATE
が空のセットを返し、私たちは別のセッションで何かを挿入しようと想定しています。我々はこれを行う場合は、:
-- Session 1
mysql> START TRANSACTION;
Query OK, 0 rows affected (0.00 sec)
mysql> SELECT * FROM `table` WHERE `col1` = 1 AND `col2` = 1 FOR UPDATE;
Empty set (0.00 sec)
-- Session 2
mysql> START TRANSACTION;
Query OK, 0 rows affected (0.00 sec)
mysql> SELECT * FROM `table` WHERE `col1` = 1 AND `col2` = 1 FOR UPDATE;
Empty set (0.00 sec)
mysql> INSERT INTO `table` SET `col1` = 1, `col2` = 1;
ERROR 1213 (40001): Deadlock found when trying to get lock; try restarting transaction
さて、私が実際にしたいことは同じで、第2 SELECT ... FOR UPDATE
ですが(まだ存在しない)行は同じようにINSERT INTO
意志をお待ちしております。テーブル全体をロックせずにこれを達成するにはどうすればよいですか。これは、他の行がアクセス可能である必要があるため許容できません。
SQLステートメントによってロックが設定されているものに大きな影響を与えないので、私はあなたが望むことはできないと思います。 innodbを使用すると、insert文は常に排他的な意図のロックを生成し、updateのための選択も排他的なロックを要求します。 – Shadow
問題は、特定の列がすでに存在する場合はセッション1が 'select for update'でチェックし、そうでない場合は作成して保存する競合状態です。 セッション2では、処理がPHPスクリプトでMySQLの外部で行われるため、すぐにその行があることはわかりません。行が実際に存在するには数ミリ秒かかることがあります。私はもちろんこれをハックすることができますが、私が上記のようにそれを行うことは、はるかにクリーンなソリューションIMHOになります。 – Anpan
私はあなたの質問を理解していますが、MySQLの解決策は、影響を受けるステートメントに対して異なる種類のロックを使用することです。 select文でupdate節を使用している限り、あなたは立ち往生しています。明らかに、共有モードでロックを使用した場合、その動作は異なります。しかし、私はあなたのビジネス要件については何も知らないので、このアプローチがあなたに役立つかどうかはわかりません。 – Shadow