データベースの強みの一つは、複数のクライアントから同時にアクセスし、データ更新できることです。ファイルサーバ上のファイルを開こうとして、同じファイルを他人が使用中なのでアクセスできません、といったエラーになった経験のある人も多いでしょう。データベースではこのような場合、どう処理するのでしょうか。
データベースの表は行の集まり(集合)として実現されており、同じ表に同時に複数のクライアントがアクセスした場合でも、それが異なる行であれば競合は発生しません。また同じ行であっても、それらが読み取りアクセスであれば、やはり競合は起きません。
同じ行に更新アクセスがあった場合は競合が発生しますが、このときのデータ保全のために、ロックという仕組みがあります。クライアントがデータ更新を要求する場合は、変更に先立ち、まずデータのロックを取得します。ロックを取得したクライアントはデータを更新できます。
一方で、データを更新したいけれど他者によってロックされていた、という場合は、ロック待ちの状態になります。ロックを取得しなければデータ更新をすることはできません。他者がデータ更新を完了し、ロックを解放した時点で初めてロックが取得でき、データを更新できるようになります。
単純なデータ更新しか行わない場合は、これであまり重大な問題は発生しませんが、トランザクションを利用した更新があると、デッドロックが発生することがあります。
クライアントXとクライアントYが同時にデータベースにアクセスしており、いずれもトランザクション機能を使って、データAとデータBの両方を更新したい、という場合を想定します。
まず、XはデータAを更新(ロックを取得)、YはデータBを更新(ロックを取得)するとします。この時点ではいずれのトランザクションもまだ完了していないので、ロックは解放されていません。
続いてXはデータBを更新するためにロックを取得しようとしますが、BのロックはYが保持しているので、Xはロック待ち状態になります。
さらにYがデータAを更新しようとしますが、AのロックはXが保持しているので、Yもロック待ちになります。
このとき、XはYがロックを解放するのを待ち、YはXがロックを解放するのを待つ、という状態になり、どちらも処理を先に進めることができなくなります。
このような状態のことをデッドロックと呼びます。この例ではクライアントが2つですが、3つ以上のクライアントで発生するケースもあります。
デッドロックが発生したときは、そのうちの一つのトランザクションを強制的に終了(ROLLBACK)させるしかありません。実際には、個々のトランザクションが短時間で終わるように工夫することでデッドロックの発生確率を下げることもできますし、データの更新順序を工夫することでデッドロックを回避できる場合もありますので、アプリケーション開発にあたってはデッドロックがなるべく発生しないような考慮が必要となります。例えば、上の例ではXがA→B、YがB→Aの順でデータ更新をしようとしたのでデッドロックが発生しましたが、YもA→Bの順でデータ更新をするならば、ロックのタイミングの如何に関わらず、デッドロックは発生しません。
そのような工夫にも関わらずデッドロックが発生してしまった場合ですが、PostgreSQLにはデッドロックを自動的に検出して、それを解決する仕組みがあります。具体的には、deadlock_timeout というパラメータで、デッドロックの検出処理を開始するまでの待ち時間をミリ秒単位で指定します。デフォルト値は1000なので1秒です。あるトランザクションが、deadlock_timeoutで指定した時間を超えてロック待ちしている場合、PostgreSQLのサーバは、デッドロックの可能性があると判断してロック状態の検査を始めます。実際にデッドロックが検出されれば、1つのトランザクションを強制的にロールバックすることでデッドロックを解決しますが、ロック待ちの時間が長いからといって必ずしもデッドロックが発生しているとは限らず、デッドロックが検出されなければ何も起きない(ロック待ちのトランザクションはそのまま)ということに注意してください。デッドロックの検出は、サーバへの負荷が比較的重い処理なので、高頻度で実行させるべきではありません。マニュアルでも、実用上は、特に負荷の大きいサーバでは、deadlock_timeoutをデフォルトの1秒より大きな値にすることを勧めています。
http://www.postgresql.jp/document/current/html/runtime-config-locks.html
解説:松田神一
© EDUCO All Rights Reserved.