Documentation Home
MySQL 5.6 リファレンスマニュアル
Download this Manual
PDF (US Ltr) - 27.1Mb
PDF (A4) - 27.2Mb
HTML Download (TGZ) - 7.2Mb
HTML Download (Zip) - 7.2Mb


MySQL 5.6 リファレンスマニュアル  /  ...  /  ロック読み取り (SELECT ... FOR UPDATE および SELECT ... LOCK IN SHARE MODE)

14.2.5 ロック読み取り (SELECT ... FOR UPDATE および SELECT ... LOCK IN SHARE MODE)

データのクエリーを実行してから、同じトランザクション内で関連データを挿入または更新する場合は、通常の SELECT ステートメントで十分な保護が提供されません。ほかのトランザクションは、クエリーが実行されたばかりの同じ行を更新または削除できます。InnoDB では、追加の安全性が提供される 2 つのタイプのロック読み取りがサポートされています。

  • SELECT ... LOCK IN SHARE MODE は、読み取られるすべての行に共有モードのロックを設定します。ほかのセッションもその行を読み取ることができますが、トランザクションがコミットするまで変更することはできません。これらの行のいずれかがコミットされていない別のトランザクションによって変更された場合、クエリーはそのトランザクションが終了するまで待機してから、最新の値を使用します。

  • 検索でインデックスレコードが見つかった場合、SELECT ... FOR UPDATE は、行および関連付けられたすべてのエントリをロックします。この動作は、これらの行に UPDATE ステートメントを発行した場合と同じです。ほかのトランザクションは、これらの行の更新、SELECT ... LOCK IN SHARE MODE の実行、または特定のトランザクション分離レベルでのデータの読み取りからブロックされます。一貫性読み取りでは、読み取られたビュー内に存在するレコードに設定されたロックはすべて無視されます。(古いバージョンのレコードはロックできません。レコードのインメモリーコピー上の Undo ログに適用することで、再構築されます。)

これらの句は、主に、単一のテーブル内または複数のテーブルに分割された状態で、ツリー構造またはグラフ構造のデータを処理する際に役立ちます。エッジまたはツリー分岐をある場所から別の場所にトラバースしても、これらのポインタに戻ってその値を変更する権利を保有しています。

トランザクションがコミットまたはロールバックされると、LOCK IN SHARE MODE および FOR UPDATE クエリーで設定されたすべてのロックが解放されます。

注記

SELECT FOR UPDATE を使用した更新対象の行のロックは、START TRANSACTION でトランザクションを開始するか、autocommit を 0 に設定することで、自動コミットが無効になっている場合にのみ適用されます。自動コミットが有効になっている場合は、指定に一致する行がロックされません。

使用例

child テーブルに新しい行を挿入し、子の行が parent テーブル内に親の行を持っていることを確認すると仮定します。アプリケーションコードを使用して、この操作シーケンス全体の参照整合性を確保できます。

まず、一貫性読み取りを使用して、PARENT テーブルでクエリーを実行し、親の行が存在することを確認します。CHILD テーブルに子の行を安全に挿入できますか。気付かないうちに、その他の一部のセッションで、SELECTINSERT との間に親の行が削除された可能性もあるため、できません。

このような問題の可能性を回避するには、LOCK IN SHARE MODE を使用して SELECT を実行します。

SELECT * FROM parent WHERE NAME = 'Jones' LOCK IN SHARE MODE;

LOCK IN SHARE MODE クエリーから「Jones」という親が返されたら、CHILD テーブルに子のレコードを安全に追加し、トランザクションをコミットできます。PARENT テーブル内のアプリケーション行で読み取りまたは書き込みを行おうとするトランザクションは、ユーザーが完了するまで (つまり、すべてのテーブル内のデータが一貫性のある状態になるまで) 待機します。

もう 1 つの例では、CHILD テーブルに追加された各子に一意の識別子を割り当てる際に使用される CHILD_CODES テーブル内の整数カウンタフィールドを検討します。一貫性読み取りまたは共有モード読み取りを使用すると、データベースの 2 人のユーザーが同じカウンタ値を参照する可能性があり、2 つのトランザクションが同じ識別子を持つ行を CHILD テーブルに追加しようとすると、重複キーのエラーが発生するため、カウンタの現在の値を読み取る際には使用しないでください。

ここで、2 人のユーザーがカウンタを同時に読み取る場合、少なくとも 1 人のユーザーがカウンタを更新しようとするとデッドロックが発生するため、LOCK IN SHARE MODE は適切な解決策ではありません。

カウンタの読み取りおよび増分を実装するには、まず FOR UPDATE を使用してカウンタのロック読み取りを実行してから、カウンタを増分します。例:

SELECT counter_field FROM child_codes FOR UPDATE;
UPDATE child_codes SET counter_field = counter_field + 1;

SELECT ... FOR UPDATE は使用可能な最新データを読み取り、読み取られる各行上に排他ロックを設定します。したがって、検索された SQL UPDATE によって行上に設定される場合と同じロックが設定されます。

前述の説明は、単に SELECT ... FOR UPDATE がどのように機能するのかを示した例です。MySQL では、テーブルへの単一アクセスを使用するだけで、一意の識別子を生成する特定のタスクを実現できます。

UPDATE child_codes SET counter_field = LAST_INSERT_ID(counter_field + 1);
SELECT LAST_INSERT_ID();

この SELECT ステートメントは、単に (現在の接続に固有の) 識別子情報を取得するだけです。どのテーブルにもアクセスしません。


User Comments
  Posted by Viktor Söderqvist on August 26, 2009
If you just want to lock a bunch of rows, without fetching any data, you can group them together using a dummy GROUP BY clause.

SELECT 1 FROM sometable WHERE somecondition GROUP BY 1 FOR UPDATE;
  Posted by on May 1, 2010
SELECT FOR UPDATE does not work when used in select statement with a subquery.
  Posted by Yvon Blais on October 28, 2011
After the example:

SELECT * FROM parent WHERE NAME = 'Jones' LOCK IN SHARE MODE;

the description:

Any transaction that tries to read or write to the applicable row in the PARENT table waits until you are finished...

should read:

Any transaction that tries to write to the applicable row in the PARENT table waits until you are finished...

Reading by other sessions *is* allowed using SHARE MODE.
  Posted by Miha Svalina on December 12, 2013
I misunderstood select lock in share mode. If second (other) transaction is not blocked to select the same row(s) in share mode than if the first transaction executes update on locked row(s), update query fails because shared lock was successfull for second transaction.

mysql> create table child_codes (counter_field integer);
Query OK, 0 rows affected (0.05 sec)

mysql> insert into child_codes set counter_field = 1;
Query OK, 1 row affected (0.00 sec)

session 2 (terminal 2):

mysql> start transaction;
Query OK, 0 rows affected (0.00 sec)

mysql> select counter_field from child_codes lock in share mode;
+---------------+
| counter_field |
+---------------+
| 1 |
+---------------+
1 row in set (0.00 sec)

session 1 (terminal 1):

mysql> start transaction;
Query OK, 0 rows affected (0.00 sec)
mysql> select counter_field from child_codes lock in share mode;
+---------------+
| counter_field |
+---------------+
| 1 |
+---------------+

session 2 (terminal 2):

mysql> update child_codes set counter_field = 2;
ERROR 1205 (HY000): Lock wait timeout exceeded; try restarting transaction

Sign Up Login You must be logged in to post a comment.