MySQL 8.0 リファレンスマニュアル


15.7.5.3 デッドロックを最小化および処理する方法

このセクションは、セクション15.7.5.2「デッドロック検出」に示したデッドロックに関する概念情報に基づいています。 ここでは、デッドロックが最小限になるようにデータベース操作を編成する方法、およびアプリケーションで必要となる後続のエラー処理について説明します。

デッドロックは、トランザクションデータベースの古典的な問題ですが、特定のトランザクションをまったく実行できないほど発生頻度が高くなければ、危険ではありません。 通常は、デッドロックが発生したためにトランザクションがロールバックされた場合に、それを再発行できる準備が常にできているようにアプリケーションを作成する必要があります。

InnoDB では自動行レベルロックが使用されます。 単一の行を挿入または削除するだけのトランザクションの場合でも、デッドロックが発生する可能性があります。 その原因は、これらの操作が実際には原子的でないためです。これらの操作では自動的に、挿入または削除される行のインデックスレコード (複数の可能性あり) にロックが設定されます。

次の方法を使用すれば、デッドロックに対処し、発生の可能性を減らすことができます。

  • いつでも、SHOW ENGINE INNODB STATUS コマンドを発行して、最近のデッドロックの原因を特定してください。 これは、デッドロックが回避されるようにアプリケーションを調整する際に役立ちます。

  • デッドロック警告が頻繁に発生することが懸念される場合は、innodb_print_all_deadlocks 構成オプションを有効にして、より広範なデバッグ情報を収集します。 MySQL のエラーログには、最近のデッドロックだけでなく、各デッドロックに関する情報が記録されます。 デバッグが完了したら、このオプションを無効にします。

  • デッドロックが原因でトランザクションに失敗した場合に、そのトランザクションを再発行できるように常に準備しておきます。 デッドロックは危険ではありません。 再度試してください。

  • トランザクションが競合する可能性を低くするために、トランザクションのサイズを小さく、期間を短く保ってください。

  • トランザクションが競合する可能性を低くするために、関連する一連の変更を行なった直後にトランザクションをコミットしてください。 特に、コミットされていないトランザクションを含むインタラクティブな mysql セッションは、長時間開いたままにしないでください。

  • locking reads (SELECT ... FOR UPDATE または SELECT ... FOR SHARE) を使用する場合は、READ COMMITTED などのより低い分離レベルを使用してみてください。

  • トランザクション内の複数のテーブルを変更する場合や、同じテーブル内のさまざまな行のセットを変更する場合は、毎回、これらの操作を一貫性のある順序で実行してください。 その結果、トランザクションで明示的に定義されたキューが生成され、デッドロックは発生しません。 たとえば、さまざまな場所で同様の INSERTUPDATE、および DELETE ステートメントのシーケンスを複数回コーディングするのではなく、データベース操作をアプリケーション内の関数に編成したり、ストアドルーチンを呼び出したりします。

  • テーブルに適切なインデックスを追加してください。 これにより、クエリーでスキャンする必要のあるインデックスレコード数が減少するため、ロックの設定も減少します。 MySQL サーバーがクエリーに最適であるとみなすインデックスを特定するために、EXPLAIN SELECT を使用してください。

  • ロックの使用を減らしてください。 SELECT が古いスナップショットからデータを返すことを許可できる場合は、FOR UPDATE または FOR SHARE 句を追加しないでください。 同じトランザクション内の各一貫性読み取りでは、独自の新しいスナップショットから読み取られるため、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 のテーブルレベルロックを使用してデッドロックを解決するには、タイムアウト方式を使用する必要があります。


関連キーワード:  InnoDB, テーブル, トランザクション, デッドロック, ロック, 構成, インデックス, 圧縮, スペース, 情報