デッドロック
業務アプリを作成していると、必ずデッドロックというものは意識すると思います。リレーショナルDBを使用しているのであれば尚更だと思います。
基本的にはテーブルの更新順序に対する標準を作っておけば、起こることはありません。
少なくともOracleでは起こらないと思ってました。(;´Д`)
SQLServerだとロックエスカレーションによってページロックが発生してデッドロック!なんてことが稀にありますが、Oracleでもマルチプロセス、もしくはパラレルDMLによる大量更新時にITLの確保待ちによるデッドロックが発生する場合があります。(記述が紛らわしいですが、SQLServerのページロックとはまったく別物です)
ITL
ITLとは(Interested Transaction List)の略で、変更情報や行ロックに関する情報を管理しています。リストというぐらいなので、当然複数持っています。いくつかというと、初期状態ではCREATE文のINITRANSの個数分だけ確保され、必要に応じて最大MAXTRANSの個数まで拡張されます。
ただし、ブロックに空き容量がない場合は、拡張されません。(拡張できません)
リストの内容はセッションごとに管理されているので、ブロックの情報を2つのセッションで同時に操作する場合は2つ必要です。3つのセッションで同時に操作する場合は3つ必要となります。ただ、ITLは有限個しかないので、大量のセッションから同時に操作されたときに、ITLを拡張できない状態にあるとどうなるかというと、ITLの開放待ちになります。
…開放待ちになります…
デッドロック発生までの流れ
まずは、ブロックに対してITLの拡張ができないほど空きがなく、且つ、ITLが1つしかないものとします。その状態で
- セッションAがブロック1の情報を更新
- セッションBがブロック2の情報を更新
- セッションAがブロック2の情報を更新しようとしてITL開放待ち
- セッションBがブロック1の情報を更新しようとしてITL開放待ち
でデッドロックが発生します。ちなみに、セッションA、Bが更新する内容で行レベルのロックは一つも被っていませんが、まったくもって有り難くないORA-00060が発生しちゃいます。
調査方法
正直、分かりにくいです。普通にORA-00060が発生するので、一般的なデッドロックと判別がつきません。V$LOCKやV$LOCKED_OBJECTをリアルタイムに参照するのも難しいことから、唯一分かる情報としては「udump」に吐かれるトレース情報ぐらいです。
ここにはデッドロック時に「待ち」が発生していたセッション情報も含めて、大量の情報が記録されています。この中に「ITL Entry」に対するブロッキングが発生していた旨があった場合、ほぼ間違いなくITLデッドロックだと思います。
また、ITLデッドロックはテーブルだけでなく、インデックスに対しても発生するため、ロック対象のROW IDやOBJECT IDにインデックスのOBJECT IDが含まれていたら、ITLデッドロックなどのアプリデッドロック以外を疑ったほうがよいです。
解決方法
基本的にINITRANSに適切な値を設定することで回避できる問題なので、マルチプロセスでの大量更新・登録系って聞いた瞬間にINITRANSを適切に設定してやることで回避できます。ただし、INITRANSは1つ増やすごとに24バイトのブロックヘッダを消費するので、ご利用は計画的に。
参考文献
SHIFT The Oracle - Interested Transaction List
Oracle parallel DML: Deadlock Detected: ORA-00060