SQLにおける「デッドロック」とは?具体例や対策方法をわかりやすく解説

データベースの操作をしているとき、データにロックがかかってしまい、処理を続けられなくなることがあります。

このとき、データでは「デッドロック」という現象が起こっていて、どこからのリクエストも受け付けなくなってしまっています。

プログラム全般において往々にして発生する現象で、多くの対策方法が考案されています。

この記事では、デッドロックとはなんなのか、どうしてデッドロックが起こってしまうのかといったことを解説していきます。

さまざまな要因で発生するデッドロックは理解が難しいのですが、同一サーバーで起こるものから、サーバー同士で発生するものまで、具体的な例をあげて、わかりやすく説明します。

また、デッドロックをゼロにすることはほとんど不可能なのですが、基本的な対策方法をいくつか紹介します。

デッドロックとは

互いが「待ち」の状態になり、処理が進められなくなるデッドロック。

「手詰まり」や「膠着状態」といった意味で、ちょっとしたミスでも発生する現象です。

まずは、デッドロックがどのようなものなのか、その概要と原因をかんたんに解説します。

あわせて、デッドロックが発生したらどうすればよいのかを軽く解説します。

お互いがロックしあうことで処理ができなくなる現象

デッドロックとは、複数のプロセスが共有資源にアクセスした際に、お互いがロックしあうことで処理が停滞してしまう現象です。

例えばSQLにおいて、データの編集を行う際、他のシステムやクエリがそのデータを触れない様に、「ロック」をします。

そして、仮に自分が編集しようと思ったデータがロックされていた場合、ロックが解除されるまで待つ事があります。

プロセス同士がバッティングしないための処置の整理がうまくいかず、処理が停滞、ロック解除をお互いに待つことしかできない状態を指してデッドロックと呼びます。

古くはチェスの用語でしたが、現在はプログラミングやコンピュータ全般において、このような現象を指す言葉として使われています。

こちらのプロセスが進まない原因が相手にあり、相手のプロセスが終わってからでなければこちらも動けない。

しかし、相手のプロセスが進まない原因がこちらにある。

こうしたことでプロセス全体が停止してしまうことが、プログラムでは往々にして発生し、そのための対策もさまざま考案されています。

SQLのデッドロックはなぜ起こる?

では、SQLのデッドロックはなぜ発生するのでしょうか。

その原因は、複数のプロセスが共有資源へ同時にアクセスしても問題なくタスクをおこなえるようにする「排他制御」にあります。

たとえば、ひとつのデータに2人が同時にアクセスして作業をして、はじめにAというデータが書き込まれ、次にBというデータが書き込まれたとします。

このとき、データ上にはAが反映されておらず、時間的に後から書き込まれたBのみがデータに反映されることがあります。

これを回避するために、排他制御を実装して、参照・更新の交通整理をおこないます。

排他制御には、そもそも同時にアクセスさせない「ロック」や、使用中かどうかの判定をおこなう「ミューテック」、アクセス数を絞る「セマフォ」などがあり、どれも、処理がバッティングしないように、共有資源にロックをかけて対処します。

この排他制御が働き、共有資源がロックされることで発生するのがデッドロックです。

排他制御によるロックの設定がうまくいっていない場合や、アクセスにミスがあった場合、すべてのプロセスが「ロック解除待ち」になり、処理が停止してしまうのです。

デッドロックが発生したら

デッドロックが発生した場合、どうすればよいのでしょうか。

まず、利用者側からは、少し待つことでロックが解消されるため、とくにやることはありません。

ほとんどのデータベース管理システム(DBMS)には、デッドロックを自動で解消する機能が備わっており、デッドロックを検知して、プロセスを強制終了することで、解決してくれます。

そして、データベースを構築する側からは、ロック順序の調整などでデッドロックを解消することになりますが、とくに大規模なデータベースになると、発生をゼロにすることはほぼ不可能であるため、なるべくデッドロックが起こりにくい対策や方針をたてる必要があります。

デッドロックの具体例

さまざまなケースで発生するデッドロック。

そのため、デッドロックには数多くの種類があります。

ここでは、代表的なデッドロックの例として、「単独プロセスでのデッドロック」「同一サーバーでのデッドロック」「複数サーバー間でのデッドロック」を紹介します。

単独プロセスでのデッドロックの具体例

まずは、ひとつのプロセスによって発生するデッドロックの例です。

プロセスがデータベース上の行を参照・更新したあとに、すぐにリクエストをおこなうことで、行にロックがかかり、それ以降の処理ができなくなるという事例です。

これは、不用意にロックをかけないことや、ロック時間の調整をすることで回避できます。

再帰関数を用いて複数回アクセスするようなメソッドを使用する際に注意が必要な例です。

同一サーバーでの具体例①

同一サーバーで発生するデッドロックとして、2つのトランザクションが、同一キーを保持した行に対してアクセスした場合に、排他制御が働いてデッドロックに陥るというものがあります。

AとB、ふたつのプロセスがおこなわれたとします。

まずAがキーを参照します。

このときBはデータの更新をおこなっています。次にAがデータを参照するのですが、ここでBがデータのキーを更新してしまいます。

これによって排他制御が働き、AとBどちらも、これ以上進行できなくなってしまいます。

これが、もっともシンプルなデッドロックの例です。

この例は、アクセス順序を整理することで減らすことができます。

同一サーバーでの具体例②

もうひとつ、同一サーバーで発生するデッドロックとして、ページ排他によるデッドロックを紹介します。

AとB、ふたつのプロセスが、ページ1とページ2に対して処理をおこなおうとします。

まずAがページ1の行1を更新し、次にBがページ2の行4を更新します。

次にAがページ2の行2を更新し、Bがページ1の行3を更新します。

ここで、ページへの行の格納順序に問題が発生し、アクセス順序を統一できず、デッドロックが発生してしまいます。

この例を回避するためには、ページ排他から行排他へ変更する必要があります。

できるかぎり行排他を採用するというのも、有効な対策です。

サーバー間で発生するデッドロックの具体例

デッドロックは、同一サーバー上だけではなく、複数のサーバー間でも発生します。

サーバー間で発生するデッドロックは「グローバルデッドロック」と呼ばれます。

まず、フロントエンドサーバーがサーバー1とサーバー2に対して、それぞれ検索リクエストを出します。

ここで、リクエストはサーバー1→サーバー2の順番でおこなわれています。

次に、更新リクエストをサーバー2→サーバー1の順序でおこなうことで、デッドロックが発生します。

これは、別のサーバーに対して、参照と更新を逆の順番でおこなうことで起こりました。

サーバー内では参照と更新の排他制御となっていますが、フロントエンドサーバーから見ると、サーバー同士のロック解除待ちとなっており、デッドロックとなります。

グローバルデッドロックも、同一サーバー上で発生するものと同様に、参照と更新のバッティングで生じます。

そのため、アクセス順序を工夫することで、発生を減らすことが可能です

デッドロックの対策方法

多様な原因で発生するデッドロック。

完全にゼロにすることは非常に難しいのですが、対策をすることで発生を減らすことが可能です。

ここでは、デッドロック回避のための基本的な対策方法を紹介します。

一般的なデッドロックの原因

具体例でみてきたようなデッドロックの原因をまとめると、以下のようになります。

・複数のプロセスが共有資源へ同時にアクセスする。
・プロセスがひとつの共有資源をロックした状態で、他の資源をロックしようとする。
・ロックをかける順番がうまく調節できていない。
・プロセスの参照・更新が逆順でおこなわる。

これらが典型的なデッドロックの原因となっています。

これらに対処するためには、「ロック順序を調整する」「不用意にロックを取得しない」「ロックを長期間保持しない」といった対策が基本となります。

ロック順序の調整

まずは、データのロック順序を調節することが重要です。

このためには、タスク同士でのロック順序を統一させる必要があります。

参照・更新を逆順でリクエストすることで発生するようなデッドロックは、この対策で回避することが可能です。

複数のデータでロックをかけるのであれば、すべてのスレッドでロック順序を統一しましょう。

また、再帰的なモジュールを使用する場合は、ロックを解除しておく必要があります。

ちなみに、デッドロックは排他制御の適用範囲を広くすることで回避できるのですが、それでは処理そのものがやりにくく、無用なデータベースとなってしまうため、このようにロックを調整する必要があるのです。

不用意にロックをかけない

再帰的モジュールの場合もそうですが、不用意にロックをかけることでデッドロックが発生しやすくなります。

ロックの設定を強くしてしまうと、デッドロックが頻発するため、まずは全体をゆるく設定して、渋滞が起こりやすい場所を発見してからロックを調整することが大切です。

ロックを長期間保持しない

データにロックをかけている時間が短ければ、プロセス同士がバッティングすることが少なくなります。

ひとつの処理がロックを長期間保持しないように調整する必要があります。

安全なシステム開発はAMELAに

今回は、SQLにおけるデッドロックについて見てきました。

本文中にも記載しましたが、ある程度は回避できるデッドロックも、仕組み次第では避けられないケースもあります。

しかし、反対にトランザクション処理の速度を上げることや、運用方法を検討することで、極力業務に支障が起きない形での開発も可能です。

このあたりは、システムを依頼する際に見えにくいポイントとも言えるでしょう。

AMELAでは、業務システムの開発実績も多く、優秀なエンジニアが多数在籍しています。

「ウチのシステム、既に結構古いけど大丈夫かな」

そう考えられた方は、是非ご相談ください。