記事一覧へ戻る

掲載元
Oracle Magazine
2014年1/2月

テクノロジー:PL/SQL

  

高度なコール・スタック分析

Steven Feuerstein著Oracle ACE Director

 

Oracle Database 12cのUTL_CALL_STACKパッケージを使用すると、よりよい回答が得られます。

 前号のPL/SQL Challengeの正解

 

前号の"SQL in PL/SQL Enhancements"で出題したPL/SQL Challengeの質問では、Oracle Database 12cで強化された、PL/SQLからSQLを実行する機能について質問しました。このクイズでは新しいDBMS_SQL .return_resultプロシージャの実例を示しましたが、3つの選択肢はすべて正解でした。

Oracle Database 12c Release 1の新しいPL/SQL機能に関する最後の記事となるこの3回目では、新しいUTL_CALL_STACKパッケージについて説明します。

コール・スタック、エラー・スタック、およびエラー・バックトレース

Oracle Database 12cより前のOracle Databaseには、コードを開発、デバッグ、および保守する際にプログラマーのおもな質問に答えてくれるいくつかのDBMS_UTILITYファンクションがあり、これが極めて役に立ってきました。とはいえ、改善の余地は常にあり、そこでOracle Database 12cにUTL_CALL_STACKが追加されました。

UTL_CALL_STACKについて詳しく説明する前に、新しいUTL_CALL_STACKパッケージによって新たな概念が作られた3つのDBMS_UTILITYファンクションについて、改めて説明します。

DBMS_UTILITY.FORMAT_CALL_STACK。Oracle7で導入されたDBMS_UTILITY.FORMAT_CALL_STACK組込みファンクションは、実行コール・スタックを表す書式設定された文字列、すなわち、ファンクションがコールされたポイントに至るまでのプロシージャまたは当該ファンクションの起動順序を示す文字列を返します。つまり、このファンクションは、「どのようにしてここにたどり着いたのでしょうか。」という質問に答えてくれます。

リスト1は、DBMS_UTILITY.FORMAT_CALL_STACKファンクションと書式設定された文字列の表示例を示しています。

コード・リスト1:DBMS_UTILITY.FORMAT_CALL_STACKファンクションの例 

SQL> CREATE OR REPLACE PROCEDURE proc1
  2  IS
  3  BEGIN
  4     DBMS_OUTPUT.put_line (DBMS_UTILITY.format_call_stack);
  5  END;
  6  /

SQL> CREATE OR REPLACE PACKAGE pkg1
  2  IS
  3     PROCEDURE proc2;
  4  END pkg1;
  5  /

SQL> CREATE OR REPLACE PACKAGE BODY pkg1
  2  IS
  3     PROCEDURE proc2
  4     IS
  5     BEGIN
  6        proc1;
  7     END;
  8  END pkg1;
  9  /

SQL> CREATE OR REPLACE PROCEDURE proc3
  2  IS
  3  BEGIN
  4     FOR indx IN 1 ..1000
  5     LOOP
  6        NULL;
  7     END LOOP;
  8
  9     pkg1.proc2;
 10  END;
 11  /

SQL> BEGIN
  2     proc3;
  3  END;
  4  /

——————— PL/SQL Call Stack ———————
   object handle    line number   object name
000007FF7EA83240              4   procedure HR.PROC1
000007FF7E9CC3B0              6   package body HR.PKG1
000007FF7EA0A3B0              9   procedure HR.PROC3
000007FF7EA07C00              2   anonymous block

 

これはトレースとエラー・ロギングにとても役立つ情報ですが、DBMS_UTILITY.FORMAT_CALL_STACKファンクションとこのファンクションから返される文字列を使用するには欠点があるのも事実です。

次のステップ 


ダウンロード
 Oracle Database 12c
 Steven Feuersteinのdemo.zip SQLファイル

 テスト PL/SQLの知識

 Feuersteinのその他の記事を読む  

詳細情報
 Oracle Database 12c
 PL/SQL

  • パッケージに含まれるサブプログラムをコールすると、書式設定されたコール・スタックにはパッケージ名しか表示されません。サブプログラム名や、このパッケージ化されたサブプログラム内に定義されているネストしたサブプログラムの名前は表示されません。

  • 直前に実行されたサブプログラムの名前だけが必要な場合でも、文字列の解析が必要になります。解析は困難ではありませんが、記述および保守が必要なコードが増えてしまいます。

  • オブジェクト・ハンドル値は事実上"ノイズ"です。PL/SQL開発者(少なくともオラクル以外)は使用しません。

DBMS_UTILITY.FORMAT_ERROR_STACK。DBMS_UTILITY.FORMAT_ERROR_STACK組込みファンクションもOracle7で導入された機能ですが、これはSQLERRMと同様に現在のエラー(SQLCODEによって返される値)に関連付けられているメッセージを返します。

DBMS_UTILITY.FORMAT_ERROR_STACKファンクションは、次の2つの点がSQLERRMと異なります。

  • 最大1,899文字のエラー・メッセージを返すことができるため、エラー・スタックが長くなった場合に文字が切り捨てられる問題が発生しなくなるか、少なくとも極めて発生しづらくなります(SQLERRMの場合はたったの510文字で切り捨てられます)。

  • このファンクションにエラー・コード番号を渡すことはできず、このファンクションを使用してエラー・コードのメッセージを返すことはできません。

原則として、例外ハンドラの内側でこのファンクションをコールしてから、後の解析に備えてエラー・スタックをエラー・ログに格納する必要があります。

DBMS_UTILITY.FORMAT_ERROR_BACKTRACE。Oracle Database 10gで導入されたDBMS_UTILITY.FORMAT_ERROR_BACKTRACE組込みファンクションは、最初にエラーが発生した行までトレース・バックして、プログラムと行番号のスタックを表す書式設定された文字列を返します。

このファンクションにより、PL/SQLの機能上の大きな欠陥が補われました。Oracle9i Database以前のリリースでは、PL/SQLブロック内で例外を処理すると、エラーが発生した行(開発者にとっておそらくもっとも重要な情報)を特定できませんでした。

この情報が本当に必要な場合は、例外を処理せず、詳しいエラー・バックトレースをスクリーンに表示するか、ユーザー向けに表示する必要がありました。

DBMS_UTILITY.FORMAT_ERROR_BACKTRACEで生成される情報は極めて有用です。エラーを処理する場合は常にDBMS_UTILITY.FORMAT_ERROR_BACKTRACEファンクションをコールし、エラー・ログ表にトレースを書き込むようにすることをお勧めします。エラー原因の解明に大いに役立ちます。

それでも、DBMS_UTILITY .FORMAT_CALL_STACKファンクションと同様、重要な情報(サブプログラムの名前やエラーが発生した行の番号)は書式設定されたトレース文字列の中に埋もれています。しかも、さらに困ったことに、パッケージ内でエラーを引き起こしたサブプログラムの名前は表示されません。

このような欠点はすべて、Oracle Database 12cに新しく導入されたUTL_CALL_STACKパッケージで対処されています。

新しいUTL_CALL_STACKパッケージ

UTL_CALL_STACKは、現在実行中のサブプログラムに関する情報を提供するパッケージです。パッケージ名からすると、実行コール・スタックに関する情報しか提供されないように思えますが、エラー・スタックおよびエラー・バックトレース・データへのアクセスも提供されます。

各スタックには深さ(位置)が含まれており、このパッケージを介して使用できるようになった3種類のスタックのそれぞれで、特定の深さの情報を確認できます。つまり、書式設定された文字列を解析し、必要とする特定の情報を探す必要はなくなりました。

DBMS_UTILITY .FORMAT_CALL_STACKからUTL_CALL_STACKへの最大の改良点の1つは、ユニット修飾名(ユニット名、サブプログラムのあらゆる字句上の親、およびサブプログラム名を連結したもの)を取得できるという点です。ただし、エラー・バックトレースではこの追加情報は使用できません。表1にUTL_CALL_STACKパッケージのサブプログラムをリストし、それぞれについて説明しています。

名前 説明
BACKTRACE_DEPTH バックトレース内のバックトレース項目の数を返します。
BACKTRACE_LINE 指定したバックトレース深さのユニットの行番号を返します。
BACKTRACE_UNIT 指定したバックトレース深さのユニットの名前を返します。
CONCATENATE_SUBPROGRAM 連結形式のユニット修飾名を返します。
DYNAMIC_DEPTH コール・スタック上のサブプログラム数を返します。これには、途中で起動されたSQL、Java、および他のPL/SQL以外のコンテキストが含まれます。たとえば、AがBをコールし、BがCをコールし、CがBをコールする場合、下に存在する動的深さを1つの行として書き込まれるこのスタックは、次のようになります。

 

A B C B
4 3 2 1

ERROR_DEPTH コール・スタック上のエラー数を返します。
ERROR_MSG 指定したエラー深さのエラーのエラー・メッセージを返します。
ERROR_NUMBER 指定したエラー深さのエラーのエラー番号を返します。
LEXICAL_DEPTH 指定した動的深さのサブプログラムの字句上のネスト・レベルを返します。
OWNER 指定した動的深さのサブプログラムのユニットの所有者名を返します。
UNIT_LINE 指定した動的深さのサブプログラムのユニットの行番号を返します。
SUBPROGRAM 指定した動的深さのサブプログラムのユニット修飾名を返します。

 

表1:UTL_CALL_STACKパッケージのサブプログラム

 

最初に、UTL_CALL_STACKをどのように使用すれば、DBMS_UTILITY.FORMAT_CALL_STACKファンクションをエミュレートして完全なコール・スタックを表示できるかを説明します。これを実行するには、スタック内のエントリをそれぞれの深さで識別して繰返し処理を実行する必要があります。リスト2に示すformat_call_stack_12cプロシージャは、このとおりの処理を実行します。

コード・リスト2:UTL_CALL_STACKサブプログラムをコールするformat_call_stack_12cプロシージャ 

SQL> CREATE OR REPLACE PROCEDURE format_call_stack_12c
  2  IS
  3  BEGIN
  4     DBMS_OUTPUT.put_line (
  5        'LexDepth Depth LineNo Name');
  6     DBMS_OUTPUT.put_line (
  7        '-------- ----- ------ ----');
  8
  9     FOR the_depth IN REVERSE 1 ..
 10                          utl_call_stack.dynamic_depth ()
 11     LOOP
 12        DBMS_OUTPUT.put_line (
 13              RPAD (
 14                 utl_call_stack.lexical_depth (
 15                    the_depth),
 16                 9)
 17           || RPAD (the_depth, 5)
 18           || RPAD (
 19                 TO_CHAR (
 20                    utl_call_stack.unit_line (
 21                       the_depth),
 22                    '99'),
 23                 8)
 24           || utl_call_stack.concatenate_subprogram (
 25                 utl_call_stack.subprogram (
 26                    the_depth)));
 27     END LOOP;
 28  END;
 29  /

 

リスト2でコールされる重要なUTL_CALL_STACKパッケージ・サブプログラムは次のとおりです。

  • 9、10行目では、数値FORループを設定し、DYNAMIC_DEPTHファンクションを使用してスタック上の最後のエントリから処理を開始し、最初のエントリへと逆方向に処理を進めます。

  • 14行目では、LEXICAL_DEPTHファンクションをコールし、各エントリのスタックにおける深さを表示します。

  • 20、21行目でUNIT_LINEをコールし、プログラム・ユニットの行番号を取得します。

  • 24、25行目では、最初にSUBPROGRAMをコールし、スタックの現在の深さにあるエントリを取得します。その後、CONCATENATE_SUBPROGRAMでそのサブプログラムの完全修飾名を取得します。

続いて、pkg.do_stuffプロシージャ内でformat_call_stack_12cプロシージャ(リスト2)を使用して、このプロシージャを実行します(リスト3参照)。

コード・リスト3:format_call_stack_12cプロシージャをコールするpkg.do_stuffプロシージャ 

SQL> CREATE OR REPLACE PACKAGE pkg
  2  IS
  3     PROCEDURE do_stuff;
  4  END;
  5  /

SQL> CREATE OR REPLACE PACKAGE BODY pkg
  2  IS
  3     PROCEDURE do_stuff
  4     IS
  5        PROCEDURE np1
  6        IS
  7           PROCEDURE np2
  8           IS
  9              PROCEDURE np3
 10              IS
 11              BEGIN
 12                 format_call_stack_12c;
 13              END;
 14           BEGIN
 15              np3;
 16           END;
 17        BEGIN
 18           np2;
 19        END;
 20     BEGIN
 21        np1;
 22     END;
 23  END;
 24  /

SQL> BEGIN
  2     pkg.do_stuff;
  3  END;
  4  /

LexDepth  Depth   LineNo     Name
———————   ——————— ————————   ——————————————————————————
0         6       2          __anonymous_block
1         5      21          PKG.DO_STUFF
2         4      18          PKG.DO_STUFF.NP1
3         3      15          PKG.DO_STUFF.NP1.NP2
4         2      12          PKG.DO_STUFF.NP1.NP2.NP3
0         1      12          FORMAT_CALL_STACK_12C

 

次は、UTL_CALL_STACKパッケージを使用して、プログラム・ユニットの名前とこのユニットの中で現在の例外が発生した行番号を表示します。リスト4では、UTL_CALL_STACKサブプログラムのコールを"隠ぺい"するBACKTRACE_TOという名前のファンクションを作成して実行しています。BACKTRACE_UNITとBACKTRACE_LINEをコールするたびに、ERROR_DEPTHファンクションで返される値を渡します。

コード・リスト4:UTL_CALL_STACKサブプログラムをコールするbacktrace_toファンクション 

 
SQL> CREATE OR REPLACE FUNCTION backtrace_to
  2     RETURN VARCHAR2
  3  IS
  4  BEGIN
  5     RETURN
  6        utl_call_stack.backtrace_unit (
  7           utl_call_stack.error_depth)
  8        || ' line '
  9        ||
 10        utl_call_stack.backtrace_line (
 11           utl_call_stack.error_depth);
 12  END;
 13  /

SQL> CREATE OR REPLACE PACKAGE pkg1
  2  IS
  3     PROCEDURE proc1;
  4     PROCEDURE proc2;
  5  END;
  6  /

SQL> CREATE OR REPLACE PACKAGE BODY pkg1
  2  IS
  3     PROCEDURE proc1
  4     IS
  5        PROCEDURE nested_in_proc1
  6        IS
  7        BEGIN
  8           RAISE VALUE_ERROR;
  9        END;
 10     BEGIN
 11        nested_in_proc1;
 12     END;
 13
 14     PROCEDURE proc2
 15     IS
 16     BEGIN
 17        proc1;
 18     EXCEPTION
 19        WHEN OTHERS THEN RAISE NO_DATA_FOUND;
 20     END;
 21  END pkg1;
 22  /

SQL> CREATE OR REPLACE PROCEDURE proc3
  2  IS
  3  BEGIN
  4     pkg1.proc2;
  5  END;
  6  /

SQL> BEGIN
  2     proc3;
  3  EXCEPTION
  4     WHEN OTHERS
  5     THEN
  6        DBMS_OUTPUT.put_line (backtrace_to);
  7  END;
  8  /

HR.PKG1 line 19

 

エラー・バックトレースの深さの値は、コール・スタックの深さの値とは異なります。コール・スタックの場合は、1がスタックの最上位(現在実行中のサブプログラム)です。エラー・バックトレースの場合は、エラーが発生したコード内の位置はERROR_DEPTHで示され、1ではありません。

UTL_CALL_STACKを使用すると、DBMS_UTILITY.FORMAT_ERROR_BACKTRACEを使用する場合に必要となる完全なバックトレース文字列の解析は不要になります。代わりに、必要となる重要な情報を極めて正確に検索、表示、ロギングできます。

UTL_CALL_STACKに関する注意点は次のとおりです。

  • 最適化のプロセスはサブプログラムの起動の省略を意味する場合があるため、コンパイラによる最適化で字句上の深さ、動的深さ、およびバックトレース深さが変わる可能性があります。

  • UTL_CALL_STACKは、過去のリモート・プロシージャ・コールの境界をサポートしません。たとえば、proc1がリモート・プロシージャremoteproc2をコールする場合は、remoteproc2がproc1に関する情報をUTL_CALL_STACKで取得することはできません。

  • 字句単位情報はUTL_CALL_STACKを通じて公開されません。代わりに、PL/SQL条件付きコンパイルを使用してこの情報を取得することができます。

UTL_CALL_STACKは非常に便利なユーティリティですが、実際に使用する場合は、このパッケージのサブプログラムを活用した独自のユーティリティをいくつか構築することが必要になるでしょう。そこで、皆さんの役に立つように、複数のユーティリティを使用したヘルパー・パッケージを作成しました。このコードは、12c_utl_call_stack_helper.sqlファイルと12c_utl_call_stack_helper_demo.sqlファイルに含まれています。

診断の向上でプログラミングが向上

3つのDBMS_UTILITYファンクション(DBMS_UTILITY.FORMAT_CALL_STACK、DBMS_UTILITY.FORMAT_ERROR_STACK、およびDBMS_UTILITY.FORMAT_ERROR_ BACKTRACE)はPL/SQLコードに潜む問題の診断と解決に大いに役立ってきました。UTL_CALL_STACKパッケージはこのデータの重要性を認識した上で、PL/SQLの開発者がより詳細かつ有用な情報にアクセスするうえで、大幅な進歩を遂げています。



クイズにチャレンジ

 

Steven FeuersteinがOracle Magazineに投稿しているPL/SQLに関するそれぞれの記事では、記事の中で説明した情報の知識をテストするクイズを毎回出題しています。このクイズは以下の他、PL/SQL Challenge(plsqlchallenge.com)にも掲載されています。PL/SQL Challengeは、PL/SQL言語やSQL、Oracle Application Express、データベース設計、および演繹論理に関するオンライン・クイズを提供するWebサイトです。

この記事のクイズ:

パッケージ仕様部を作成します。

CREATE OR REPLACE PACKAGE plch_pkg
IS
   PROCEDURE proc1;
END plch_pkg;
/
次のブロックを実行した後にNESTED_IN_PROC1というテキストをスクリーンに表示するように、このパッケージの本体を作成するのはどの選択肢でしょうか。

 

EXEC plch_pkg.proc1

a.

CREATE OR REPLACE PACKAGE BODY plch_pkg
IS
  PROCEDURE proc1
  IS
    PROCEDURE nested_in_proc1
    IS
    BEGIN
      DBMS_OUTPUT.put_line (DBMS_UTILITY.format_call_stack);
    END;
  BEGIN
    nested_in_proc1;
  END;
END plch_pkg;
/

b.

CREATE OR REPLACE PACKAGE BODY plch_pkg
IS
  PROCEDURE proc1
  IS
    PROCEDURE nested_in_proc1
    IS
    BEGIN
      DBMS_OUTPUT.put_line (
         UTL_CALL_STACK.CONCATENATE_SUBPROGRAM (
          UTL_CALL_STACK.SUBPROGRAM (1)));
    END;
  BEGIN
    nested_in_proc1;
  END;
END plch_pkg;
/ 



c.
 

CREATE OR REPLACE PACKAGE BODY plch_pkg
IS
  PROCEDURE proc1
  IS
    PROCEDURE nested_in_proc1
    IS
    BEGIN
      DBMS_OUTPUT.put_line ($$plsql_unit);
    END;
  BEGIN
    nested_in_proc1;
  END;
END plch_pkg;
/


Steven Feuersteinの顔写真


Steven Feuersteinの略歴および彼がOracle Magazineに投稿したPL/SQLに関する他の記事

 





ご意見ご感想をお寄せください。

▲ ページTOPに戻る

記事一覧へ戻る