デッドロックはトランザクションデータベースの中ではよく知られている問題ですが、ある特定のトランザクションをまったく起動できないほど頻繁に起きる訳ではないのならば危険ではありません。通常は、トランザクションがデッドロックのためにロールバックされたらそれを再発行できる準備が常にできているように、アプリケーションを書き込まなければいけません。
InnoDB
は自動行レベルロックを利用します。単一行を挿入または削除するだけのトランザクションの場合でもデッドロックを得ることができます。それは、これらの操作が実際には
「原始的」
でないからです。これらの操作は自動的に、挿入または削除する行のインデックスレコード
(複数の可能性あり) にロックを設定します。
次のテクニックを利用して、デッドロックに対処し、それらの発生の可能性を減らすことができます:
SHOW
ENGINE INNODB STATUS
を利用して最新のデッドロックの原因を究明してください。それで、アプリケーションがデッドロックを防ぐ様に調整することができます。
トランザクションがデッドロックのせいで失敗したら再発行できるように常に準備しておいてください。デッドロックは危険ではありません。もう一度やってみてください。
トランザクションを頻繁にコミットしてください。小さいトランザクションは衝突の傾向が少ないです。
もしロック読み取り
(SELECT
... FOR UPDATE
または
SELECT ... LOCK
IN SHARE MODE
)
を利用しているなら、READ
COMMITTED
のような低遮断レベルを利用するように試みてください。
決まった順番でテーブルと行にアクセスしてください。するとトランザクションは明確な列になりデッドロックしません。
テーブルに適切なインデックスを追加してください。するとクエリーが走査しなければいけないインデックスレコードが減り、その結果ロックの設定が減ります。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
テーブルレベルロックでは、デッドロックを解決するためにタイムアウト法を利用しなければいけません。