他にロックがないのに ORA-00054 エラーが発生する理由と対処
本業のシステムで ORA-00054 ってエラーがたまに発生します。アプリケーション側でエラーを捕捉して適切なエラーを出しているので大事にはならないのですが、そもそも格好悪い。そもそも ORA-00054 とは何か?
というエラーが示しているとおり、
「SELECT 〜 FOR UPDATE NOWAIT; を発行した際に、該当するレコードロックが取得できなかった場合に、NOWAIT 指定によりロック取得を待たずにエラーで返しました。」
って意味になります。通常このエラーが発生する場合は、以下のような場合です。
トランザクション1 | トランザクション2 |
SELECT 〜 FOR UPDATE NOWAIT; | |
UPDATE 処理 | SELECT 〜 FOR UPDATE NOWAIT; →ORA-00054 |
COMMIT; | ×(処理できず) |
なのですが、今回のケースはコレじゃないんです。ってか、上記のようなケースはテスト時に見つかるはず。実は、ORA-00054 はこれ以外の原因でも発生します。
運用を続けていく中でしか発生しにくいパターンなのですが、ORA-54エラーは、ITL (Interested Transaction List) が不足した場合にも発生します。
Oracle のデータブロックの内部構造から考えてみる
Oracleでは、同時トランザクションによるアクセスを管理するために、各データブロック内に制御情報として、トランザクションエントリ(ITL)という領域を確保しますし、ロックを取得します。ITL は表に対してINITRANSパラメータで静的に確保されています(デフォルト値は1)。また、ITL 領域が不足した場合には、ブロック内の PCTFREE による空き領域に空きがあれば MAXTRANSで設定されている値(10g では常に 255) まで動的に確保されます。
つまり、図で表すと以下の感じ。黄色の部分が Oracle のヘッダ情報になる部分。
分類 | 要素 | 関連パラメータ | Linux X86-64 での値 |
ブロックヘッダ | 共通ヘッダ | KCBH | 20 |
固定トランザクションヘッダ | KTBBH | 48 | |
可変トランザクションヘッダ | KTBIT×(INITRANS-1) | 24 x (INITRANS-1) | |
データヘッダ | KDBH | 14 | |
表ディレクトリ | KDBT | 4 | |
行ディレクトリ | |||
空き領域 | データ | PCTFREE | |
ITL | MAXTRANS | 24 x ? | |
データ領域 | データ | PCTUSED | |
ITL | MAXTRANS | 24 x ? | |
ターミネータ | UB4 | 4 |
PCTFREE デフォルト値10%と INITRANS のデフォルト値1がマズイ点について
一般には PCTFREE パラメータを適切に設定していれば、複数の ITL を静的に確保しておく必要がないと言われていますが、データ更新により頻繁にレコード長が変わるテーブルで、かつ各レコード長が小さい場合には PCTFREE を使い切ってしまう場合があります。
PCTFREE パラメータはデフォルト値10%です。例えば、8Kのブロックサイズのデータベースだとすると、PCTFREE で確保される空き領域は僅かに 800 byte です。varchar2 型のデータをもつテーブルで頻繁に更新がある場合は PCTFREE を使い切ってしまう場合が実際あります。とすると、ITL はデフォルトの1しか確保できないので、
PCTFREE に空きがないデータブロックに対しては、トランザクションが1つしか同時実行できなくなる可能性があるのです。
ORA-00054 を解決する方法する2つの方法
まず1つめは、Oracle 9i 以降にサポートされた SELECT 〜 FOR UPDATE WAIT 秒数; を使って、デッドロックを回避しつつトランザクションを待つ SQL に変更することです。8i までは WAIT に秒数が指定できなかったので、デッドロックになってしまうのですが、秒数が指定できるので、5秒まってダメならエラーなんて感じにできます。完全な対処ではなく、ORA-54 を減らすという解決方法なのですが、アプリケーションの改変も SQL 1つ変更するだけで、効果も十分にあります。
2つめが、INITRANS を増やし、PCTFREE をチューニングする方法です。但し、INITRANS は既に存在するデータブロックに対しては効果がありません。次に確保するデータブロックから効果がある設定(と思う。既存のデータブロックも再配置される?)なので、今まさに発生している ORA-54 が消えるというわけではないかもしれませんが、将来は発生しなくなるでしょう。変更する構文は、
ALTER TABLE テーブル名 PCTFREE 数値;
です。完全に解決したいという場合は、INITRANS と PCTFREE を適切にチューニングしてテーブルを再作成することになるでしょう。今回は、1つめの対処方法で切り抜けましたが、エラーはその後発生しなくなりました。
ITL を更に深追い。データブロックのダンプをとってみる
さらにディープな領域へいくなら、ダンプを採る方法があります。ダンプの取り方は
です。ダンプの出力先は、user_dump_dest パラメータで指定された場所です。管理者ユーザで sqlplus に接続して、
で user_dump_dest の場所は取得できますが、通常はアラートログとかがあるディレクトリと近い場所で、xxxx/DB名/udump って場所にあるはずです。データファイル番号とブロック番号は、ITL が不足しているテーブルの該当レコードに対して、以下の SQL を実行することで一覧が取得できます。
dbms_rowid.rowid_relative_fno(rowid) file#, dbms_rowid.rowid_block_number(rowid) block#
FROM テーブル名 WHERE 条件;
実行例は以下の通り。
FILE# BLOCK# ---------- ---------- 9 190106 9 224717 9 208066 9 218346 9 220914 9 230474
データファイル番号とブロック番さえわかればダンプができます。ダンプすると、udump ディレクトリに、DB名_ora_0001.trc みたいなファイルができていると思います。比較的サイズがおおきいやつがそうです。中身はこんな感じになってます。正しくデータブロックのダンプそのものをひとが読めるようにした感じのものです。
/app/oracle/admin/TESTDB/udump/TESTDB_ora_0001.trc Oracle Database 10g Enterprise Edition Release 10.2.0.1.0 - 64bit Production With the Partitioning, Real Application Clusters and Data Mining options ORACLE_HOME = /app/oracle/product/10.2.0/db System name: Linux Node name: TESTDB2 Release: 2.6.9-34.ELsmp Version: #1 SMP Fri Feb 24 16:56:28 EST 2006 Machine: x86_64 Instance name: TESTDB Redo thread mounted by this instance: 2 Oracle process number: 33 Unix process pid: 8021, image: oracle@TESTDB (TNS V1-V3) *** 2006-08-04 17:42:06.005 *** SERVICE NAME:(SYS$USERS) 2006-08-04 17:42:05.991 *** SESSION ID:(284.50151) 2006-08-04 17:42:05.991 Start dump data blocks tsn: 6 file#: 9 minblk 236899 maxblk 236899 buffer tsn: 6 rdba: 0x02439d63 (9/236899) scn: 0x0000.034ac382 seq: 0x01 flg: 0x06 tail: 0xc3820601 frmt: 0x02 chkval: 0x9e71 type: 0x06=trans data Hex dump of block: st=0, typ_found=1 Dump of memory from 0x0000000005E83E00 to 0x0000000005E85E00 005E83E00 0000A206 02439D63 034AC382 06010000 [....c.C...J.....] 005E83E10 00009E71 00000001 00003AD9 034ABFDE [q........:....J.] 〜中略〜 005E85DE0 04086A78 07010101 04086C78 FF010101 [xj......xl......] 005E85DF0 36300AFF 6C633830 52443430 C3820601 [..0608cl04DR....] Block header dump: 0x02439d63 Object id on Block? Y seg/obj: 0x3ad9 csc: 0x00.34abfde itc: 2 flg: E typ: 1 - DATA brn: 0 bdba: 0x2438422 ver: 0x01 opc: 0 inc: 0 exflg: 0 Itl Xid Uba Flag Lck Scn/Fsc 0x01 0x0007.002.00008c14 0x00800d76.0b52.21 --U- 1 fsc 0x0000.034ac382 0x02 0x0004.012.00008bb8 0x008007c5.0ca4.38 C--- 0 scn 0x0000.034abf59 data_block_dump,data header at 0x5e83e64 =============== tsiz: 0x1f98 hsiz: 0x114 pbl: 0x05e83e64 bdba: 0x02439d63 76543210 flag=-------- ntab=1 nrow=129 frre=-1 fsbo=0x114 fseo=0x3b0 avsp=0x343 tosp=0x343 0xe:pti[0] nrow=129 offs=0 0x12:pri[0] offs=0x1f64 〜中略〜 0x110:pri[127] offs=0x422 0x112:pri[128] offs=0x3ea block_row_dump: tab 0, row 0, @0x1f64 tl: 52 fb: --H-FL-- lb: 0x0 cc: 9 col 0: [ 7] c6 12 01 08 20 56 45 col 1: [ 6] 67 6c 61 79 38 38 col 2: [ 1] 30 col 3: [ 2] c2 02 col 4: [ 7] 78 6a 08 04 01 01 01 col 5: [ 7] 78 6c 08 04 01 01 01 col 6: *NULL* col 7: *NULL* col 8: [10] 30 36 30 38 63 6c 30 34 44 52 tab 0, row 1, @0x1f31 tl: 51 fb: --H-FL-- lb: 0x0 cc: 9 〜中略〜 end_of_block_dump End dump data blocks tsn: 6 file#: 9 minblk 236899 maxblk 236899
赤字の部分が ITL 制御情報になります。この例では ITL が2つ分確保されていることがわかります。ORA-54 が発生した SQL を元に、該当するデータブロックを全て見ていくことで、どのブロックで ITL が不足したかを知ることができます。この手の話を更に知りたい方は、おらおらオラクルの P119 ページが参考になります。
翔泳社 (2003/05/27)
売り上げランキング: 51,170
コメントやシェアをお願いします!
大島 徹
弊社のシステム上においてOra-00054エラーが発生し、原因がわからずこの「他にロックがないのに ORA-00054 エラーが発生する理由と対処」を大変参考にさせて頂いております。一点質問させて頂きたいのですが、このITL不足でOra-00054が発生するのは、レコードUpdate処理時のみと考えてよろしいでしょうか? insertやdeleteまたcreate index等の処理ではITLの不足による問題は発生しないと考えてよいでしょうか?
宜しくお願いします。
ヤオナタ
原因不明の排他エラーに長い間悩まされていたのですが、まさかこんな対処法があるとは!
問題が解決できそうです。ありがとうございました。