デッドロック

デッドロックは、複数のタスクが永久的に相互ブロックすることで発生します。つまり、一方のタスクがロックを試みているリソースに他方のタスクがロックを保持していて、これが相互に行われるとデッドロックが発生します。次に例を示します。

  • トランザクション A が行 1 の共有ロックを取得します。

  • トランザクション B が行 2 の共有ロックを取得します。

  • トランザクション A が行 2 の排他ロックを要求しますが、トランザクション B が完了し、B が保持している行 2 の共有ロックが解放されるまで A はブロックされます。

  • このとき、トランザクション B が行 1 の排他ロックを要求すると、トランザクション A が完了し、A が保持している行 1 の共有ロックが解放されるまで B はブロックされます。

トランザクション A は、トランザクション B が完了するまで完了できませんが、トランザクション B もトランザクション A によってブロックされます。この状態は、循環依存関係とも呼ばれます。トランザクション A がトランザクション B に依存し、トランザクション B がトランザクション A に依存するため、依存関係が循環します。

デッドロックになったどちらのトランザクションも、外部処理からデッドロックを解除されない限り、永久的に待機を続けます。Microsoft SQL Server データベース エンジンのデッドロック モニタにより、デッドロックになったタスクがあるかどうかが定期的に確認されます。モニタによって循環依存関係が検出されると、一方のタスクがデッドロックの犠牲者として選択され、そのトランザクションはエラーで終了されます。その結果、もう一方のタスクのトランザクションを完了できます。トランザクションがエラーで終了したアプリケーションは、そのトランザクションを再試行できます。通常は、デッドロックの一方のトランザクションが完了してからこのトランザクションも完了します。

アプリケーションで特定のコーディング規則を使用すると、デッドロックが発生する可能性が低くなります。詳細については、「デッドロックの最小化」を参照してください。

デッドロックが、通常のブロッキングと混同されることがあります。あるトランザクションが、別のトランザクションによってロックされているリソースのロックを要求すると、ロックを要求したトランザクションはロックが解放されるまで待機します。既定では、LOCK_TIMEOUT を設定しない限り、SQL Server のトランザクションはタイムアウトになりません。この場合、ロックを要求したトランザクションはブロックされているだけで、デッドロックが発生しているわけではありません。つまり、ロックを要求したトランザクションは、ロックを所有しているトランザクションをブロックする操作を行っていません。最終的には、ロックを所有しているトランザクションが完了してロックが解放され、ロックを要求したトランザクションがロックを取得し、続行されます。

デッドロックは、「破壊的な支配」と呼ばれることもあります。

デッドロックの状態は、リレーショナル データベース管理システムだけでなく、複数のスレッドを使用していれば、どのようなシステムでも発生する可能性があります。また、データベース オブジェクトのロック以外でも発生する可能性があります。たとえば、マルチスレッド オペレーティング システムの 1 つのスレッドが、メモリのブロックなど、1 つ以上のリソースを取得するとします。取得しようとしているリソースが別のスレッドに所有されている場合、最初のスレッドはリソースを所有しているスレッドがそのリソースを解放するまで待機することになります。このとき、待機しているスレッドのことを「そのリソースについて、所有側のスレッドに対する依存関係がある」といいます。データベース エンジンのインスタンスでは、メモリやスレッドなど、データベース以外のリソースを取得するときにデッドロックが発生する可能性があります。

トランザクションのデッドロック発生の図

この例では、トランザクション T1 は Part テーブルのロック リソースに関して、トランザクション T2 に依存関係があります。同様に、Supplier テーブルのロック リソースに関しては、トランザクション T2 がトランザクション T1 に対する依存関係を持っています。これらの依存関係は相互に働くため、トランザクション T1 と T2 の間でデッドロックが発生します。

デッドロックは、テーブルがパーティション分割されており、ALTER TABLE の LOCK_ESCALATION 設定が AUTO に設定されている場合にも発生することがあります。LOCK_ESCALATION を AUTO に設定すると、データベース エンジンが TABLE レベルではなく HoBT レベルでテーブル パーティションをロックできるようになるため、同時実行性が向上します。ただし、個々のトランザクションがテーブルのパーティション ロックを保持し、他のトランザクション パーティションのどこかをロックする必要がある場合、デッドロックが発生します。このタイプのデッドロックは、LOCK_ESCALATION を TABLE に設定することで回避できますが、この設定を行うと、パーティションに対して大規模な更新を行う際にテーブル ロックを獲得するまで待機しなければならなくなるため、同時実行性が低下します。