このセクションは、セクション14.2.10「デッドロックの検出とロールバック」に示したデッドロックに関する概念情報に基づいています。ここでは、デッドロックが最小限になるようにデータベース操作を編成する方法、およびアプリケーションで必要となる後続のエラー処理について説明します。
デッドロックは、トランザクションデータベースの古典的な問題ですが、特定のトランザクションをまったく実行できないほど発生頻度が高くなければ、危険ではありません。通常は、デッドロックが発生したためにトランザクションがロールバックされた場合に、それを再発行できる準備が常にできているようにアプリケーションを作成する必要があります。
InnoDB
では自動行レベルロックが使用されます。単一の行を挿入または削除するだけのトランザクションの場合でも、デッドロックが発生する可能性があります。その原因は、これらの操作が実際には「原子的」でないためです。これらの操作では自動的に、挿入または削除される行のインデックスレコード (複数の可能性あり) にロックが設定されます。
次の方法を使用すれば、デッドロックに対処し、発生の可能性を減らすことができます。
いつでも、
SHOW ENGINE INNODB STATUS
コマンドを発行して、最近のデッドロックの原因を特定してください。これは、デッドロックが回避されるようにアプリケーションを調整する際に役立ちます。頻繁にデッドロックの警告が発生することに懸念がある場合は、
innodb_print_all_deadlocks
構成オプションを有効にして、より広範囲にわたるデバッグ情報を収集してください。MySQL のエラーログには、最近のデッドロックだけでなく、各デッドロックに関する情報が記録されます。デバッグが完了したら、このオプションを無効にします。デッドロックが原因でトランザクションに失敗した場合に、そのトランザクションを再発行できるように常に準備しておきます。デッドロックは危険ではありません。再度試してください。
トランザクションが競合する可能性を低くするために、トランザクションのサイズを小さく、期間を短く保ってください。
トランザクションが競合する可能性を低くするために、関連する一連の変更を行なった直後にトランザクションをコミットしてください。特に、コミットされていないトランザクションを含むインタラクティブな mysql セッションは、長時間開いたままにしないでください。
ロック読み取り (
SELECT ... FOR UPDATE
またはSELECT ... LOCK IN SHARE MODE
) を使用する場合は、READ COMMITTED
などの低い分離レベルを使用してみてください。トランザクション内の複数のテーブルを変更する場合や、同じテーブル内のさまざまな行のセットを変更する場合は、毎回、これらの操作を一貫性のある順序で実行してください。その結果、トランザクションで明示的に定義されたキューが生成され、デッドロックは発生しません。たとえば、さまざまな場所で同様の
INSERT
、UPDATE
、およびDELETE
ステートメントのシーケンスを複数回コーディングするのではなく、データベース操作をアプリケーション内の関数に編成したり、ストアドルーチンを呼び出したりします。テーブルに適切なインデックスを追加してください。これにより、クエリーでスキャンする必要のあるインデックスレコード数が減少するため、ロックの設定も減少します。MySQL サーバーがクエリーに最適であるとみなすインデックスを特定するために、
EXPLAIN SELECT
を使用してください。ロックの使用を減らしてください。古いスナップショットからのデータを返すために、
SELECT
を許可する余裕がある場合は、FOR UPDATE
またはLOCK IN SHARE MODE
句を追加しないでください。同じトランザクション内の各一貫性読み取りでは、独自の新しいスナップショットから読み取られるため、READ COMMITTED
分離レベルを使用することが適切な方法です。-
ほかに方法がなければ、テーブルレベルロックを使用してトランザクションを直列化してください。
InnoDB
テーブルなどのトランザクションテーブルでLOCK TABLES
を使用する正しい方法は、(START TRANSACTION
ではなく)SET autocommit = 0
でトランザクションを開始し、そのあとLOCK TABLES
を実行し、UNLOCK TABLES
を呼び出す前にそのトランザクションを明示的にコミットすることです。たとえば、テーブルt1
に書き込み、テーブルt2
から読み取る必要がある場合は、次のように実行できます。SET autocommit=0; LOCK TABLES t1 WRITE, t2 READ, ...; ... do something with tables t1 and t2 here ... COMMIT; UNLOCK TABLES;
テーブルレベルロックを使用すると、テーブルへの並列更新が抑制されるため、デッドロックが回避されますが、負荷の高いシステムで応答性が低くなるという犠牲が伴います。
トランザクションを直列化する別の方法は、単一行だけを含む補助「セマフォー」テーブルを作成することです。ほかのテーブルにアクセスする前に、各トランザクションでその行を更新してください。これにより、すべてのトランザクションが直列方式で発生します。直列化ロックは行レベルロックであるため、この場合、
InnoDB
のインスタントデッドロック検出アルゴリズムも機能することに注意してください。MySQL のテーブルレベルロックを使用してデッドロックを解決するには、タイムアウト方式を使用する必要があります。