デッドロックはトランザクションデータベースにおける従来からの問題ですが、一部のトランザクションを実行できなくなるほど頻繁に発生するのでなければ危険ではありません。通常は、アプリケーションを作成する際に、デッドロックのためにロールバックされたトランザクションを再発行できるようにしておく必要があります。
InnoDB
は、自動で行レベルロックを使用します。単一のレコードを挿入または削除するだけのトランザクションでもデッドロックが発生します。それは、これらの操作が実際には原子性を持つものではなく、挿入/削除するレコードのインデックスレコード(おそらくは複数)に自動的にロックを設定するためです。
デッドロックに対処し、デッドロックの発生頻度を減らすための方法を次に示します。
MySQL の 3.23.52 以降または 4.0.3
以降のバージョンでは、SHOW INNODB
STATUS
を使用して最後に発生したデッドロックの原因を特定する。これは、デッドロックを回避するようにアプリケーションを調整する際に役立つ。
デッドロックで失敗したトランザクションを再発行できるようにしておく。デッドロックには危険性がないので、単に再試行すればよい。
トランザクションを頻繁にコミットする。小さなトランザクションは衝突することが少ない。
ロックを取得する読み取り SELECT ...
FOR UPDATE
または ... LOCK IN SHARE
MODE
を使用している場合は、より低い分離レベル
READ COMMITTED
を使用してみる。
テーブルとレコードには一定の順序でアクセスする。これによって、トランザクションが整然と待ち行列を形成するようになり、デッドロックが発生しなくなる。
適切に選択されたインデックスをテーブルに追加する。これによって、クエリがスキャンするインデックスレコードの数、および設定するロックの数を削減できる。
EXPLAIN SELECT
を使用して、MySQL
がクエリに対して適切なインデックスを選択することを確認する。
ロックの使用を減らす。SELECT
で古いスナップショットからデータが返されてもかまわない場合は、このステートメントに
FOR UPDATE
節や LOCK IN
SHARE MODE
節を追加しないようにする。この場合は、READ
COMMITTED
分離レベルを使用するとよい。この分離レベルでは、同じトランザクション内の一貫した読み取りそれぞれが独自の新しいスナップショットを読み取るからである。
どの方法でも解決しない場合は、テーブルレベルのロック
LOCK TABLES t1 WRITE, t2 READ, ... ; [do
something with tables t1 and t2 here]; UNLOCK
TABLES
を使ってトランザクションを直列化する。
テーブルレベルのロックによって、トランザクションが整然と待ち行列に並ぶようになり、デッドロックが回避される。LOCK
TABLES
は、BEGIN
コマンドと同様にトランザクションを暗黙的に開始し、UNLOCK
TABLES
は、COMMIT
でトランザクションを暗黙的に終了することに注意する。
トランザクションを直列化するもう 1 つの方法として、レコードが 1 つしかない補助的な 'セマフォ' テーブルを作成する。各トランザクションは、このレコードを更新してから他のテーブルにアクセスする。こうして、すべてのトランザクションが直列的に実行される。 MySQL のテーブルレベルロックでは、タイムアウトを使ってデッドロックを解決する必要がある。
This is a translation of the MySQL Reference Manual that can be found at dev.mysql.com. The original Reference Manual is in English, and this translation is not necessarily as up to date as the English version.