自律型トランザクション

自律型トランザクションは、別の、メイン・トランザクションによって開始される独立したトランザクションです。

自律型トランザクションは、メイン・トランザクションをコミットまたはロールバックせずに、SQL操作を実行してコミットまたはロールバックします。

図7-1に、メイン・トランザクション(MT)から自律型ルーチン(proc2)へ制御がどのように流れ、また戻るかを示します。自律型ルーチンは、2つの自立型トランザクション(AT1およびAT2)をコミットします。

図7-1 トランザクション制御の流れ

図7-1の説明が続きます
「図7-1 トランザクション制御フロー」の説明

ノート:

自律型トランザクションは別のトランザクションによって開始されますが、これはネストしたトランザクションではありません。その理由は次のとおりです。

  • ロックなどのトランザクション・リソースをメイン・トランザクションと共有しません。

  • メイン・トランザクションに依存しません。

    たとえば、メイン・トランザクションがロールバックする場合は、ネストしたトランザクションがロールバックするのに対し、自律型トランザクションはロールバックしません。

  • コミットされた変更を、他のトランザクションからすぐに参照できます。

    ネストしたトランザクションのコミットされた変更は、メイン・トランザクションがコミットするまで他のトランザクションからは参照できません。

  • 自律型トランザクションで例外が呼び出されると、文レベルのロールバックではなくトランザクション・レベルのロールバックが発生します。

ここでのトピック

自律型トランザクションのメリット

自律型トランザクションは、開始すると完全に独立します。ロック、リソースまたはコミット依存関係をメイン・トランザクションと共有することはありません。メイン・トランザクションがロールバックする場合でも、イベントや増分再試行カウンタなどのログを取ることができます。

自律型トランザクションは再利用可能なソフトウェア・コンポーネントであるモジュール構造の作成に役立ちます。自律型トランザクションは、ストアド・サブプログラム内部でカプセル化できます。起動元のアプリケーションは、そのストアド・サブプログラムによって実行された操作が成功したか失敗したかを知る必要はありません。

トランザクション・コンテキスト

メイン・トランザクションはそのコンテキストをネストしたルーチンと共有しますが、自律型トランザクションとは共有しません。ある自律型ルーチンが別の自律型ルーチンを(または自身を再帰的に)起動する場合、ルーチンはトランザクション・コンテキストを共有しません。ある自律型ルーチンが自律型ではないルーチンを起動する場合、ルーチンは同じトランザクション・コンテキストを共有します。

トランザクションの可視性

自律型トランザクションによって行われた変更は、自律型トランザクションがコミットすると、他のトランザクションから参照できるようになります。分離レベルがREAD COMMITTED(デフォルト)に設定されている場合、メイン・トランザクションが再開すると、これらの変更をメイン・トランザクションから参照できるようになります。

メイン・トランザクションの分離レベルをSERIALIZABLEに設定すると、その自律型トランザクションによって行われた変更は、再開してもメイン・トランザクションからは参照できません。

SET TRANSACTION ISOLATION LEVEL SERIALIZABLE;

ノート:

  • トランザクションのプロパティは、それらが設定されたトランザクションにのみ適用されます。

  • カーソル属性は自律型トランザクションの影響を受けません。

自律型ルーチンの宣言

自律型ルーチンを宣言するには、AUTONOMOUS_TRANSACTIONプラグマを使用します。

このプラグマの詳細は、「AUTONOMOUS_TRANSACTIONプラグマ」を参照してください。

ヒント:

読みやすくするために、AUTONOMOUS_TRANSACTIONプラグマは宣言部の先頭に置きます。(プラグマは宣言部の任意の場所に置くことができます。)

AUTONOMOUS_TRANSACTIONプラグマはパッケージ全体またはADT全体に適用することはできませんが、パッケージ内の各サブプログラムまたはADTの各メソッドに適用できます。

例7-43 パッケージでの自律型ファンクションの宣言

この例では、パッケージ・ファンクションを自律型としてマークします。

CREATE OR REPLACE PACKAGE emp_actions AUTHID DEFINER AS  -- package specification
  FUNCTION raise_salary (emp_id NUMBER, sal_raise NUMBER)
  RETURN NUMBER;
END emp_actions;
/
CREATE OR REPLACE PACKAGE BODY emp_actions AS  -- package body
  -- code for function raise_salary
  FUNCTION raise_salary (emp_id NUMBER, sal_raise NUMBER)
  RETURN NUMBER IS
    PRAGMA AUTONOMOUS_TRANSACTION;
    new_sal NUMBER(8,2);
  BEGIN
    UPDATE employees SET salary =
      salary + sal_raise WHERE employee_id = emp_id;
    COMMIT;
    SELECT salary INTO new_sal FROM employees
      WHERE employee_id = emp_id;
    RETURN new_sal;
  END raise_salary;
END emp_actions;
/

例7-44 自律型スタンドアロン・プロシージャの宣言

この例では、スタンドアロン・サブプログラムを自律型としてマークします。

CREATE OR REPLACE PROCEDURE lower_salary
  (emp_id NUMBER, amount NUMBER)
AUTHID DEFINER AS
  PRAGMA AUTONOMOUS_TRANSACTION;
BEGIN
  UPDATE employees
  SET salary =  salary - amount
  WHERE employee_id = emp_id;

  COMMIT;
END lower_salary;
/

例7-45 自律型PL/SQLブロックの宣言

この例では、スキーマ・レベルのPL/SQLブロックを自律型としてマークします。(ネストしたPL/SQLブロックは自律型にはできません。)

DROP TABLE emp;
CREATE TABLE emp AS SELECT * FROM employees;
 
DECLARE
  PRAGMA AUTONOMOUS_TRANSACTION;
  emp_id NUMBER(6)   := 200;
  amount NUMBER(6,2) := 200;
BEGIN
  UPDATE employees
  SET salary =  salary - amount
  WHERE employee_id = emp_id;
 
  COMMIT;
END;
/

自律型トランザクションの制御

自律型ルーチンの最初のSQL文でトランザクションが開始されます。1つのトランザクションが終了すると、次のSQL文で別のトランザクションが開始されます。カレント・トランザクションは、最後のコミットまたはロールバックよりも後に実行されたすべてのSQL文で構成されます。自律型トランザクションを制御するには、次の文を使用します(これは現在の(アクティブな)トランザクションのみに適用されます)。

  • COMMIT

  • ROLLBACK [TO savepoint_name]

  • SAVEPOINT savepoint_name

  • SET TRANSACTION

ここでのトピック

自律型ルーチンの開始と終了

自律型ルーチンの実行部に入ると、メイン・トランザクションは停止します。ルーチンを終了すると、メイン・トランザクションは再開します。

コミットまたはロールバックせずにアクティブな自律型トランザクションを終了しようとすると、データベースによって例外が呼び出されます。例外が未処理になった場合、またはその他の未処理例外が発生してトランザクションが終了した場合、トランザクションはロールバックします。

正常に終了するには、ルーチンはすべての自律型トランザクションを明示的にコミットまたはロールバックする必要があります。ルーチン(またはそれによって起動されたルーチン)に保留中のトランザクションがある場合は、PL/SQLによって例外が呼び出され、保留中のトランザクションはロールバックします。

自律型トランザクションのコミットおよびロールバック

COMMITROLLBACKはアクティブな自律型トランザクションを終了しますが、自律型ルーチンから抜けるわけではありません。1つのトランザクションが終了すると、次のSQL文で別のトランザクションが開始されます。1つの自律型ルーチンは、複数のCOMMIT文を発行する場合、複数の自律型トランザクションを含むことができます。

セーブポイント

セーブポイントの有効範囲は、それが定義されたトランザクションです。メイン・トランザクション内で定義されたセーブポイントは、その自律型トランザクション内で定義されたセーブポイントとは無関係です。実際、メイン・トランザクションと自律型トランザクションのセーブポイントには、同じ名前を使用できます。

ロールバックできるのは、カレント・トランザクション内でマークされたセーブポイントまでです。自律型トランザクション内では、メイン・トランザクション内でマークされたセーブポイントまではロールバックできません。メイン・トランザクションのセーブポイントまでロールバックするには、自律型ルーチンを抜けてメイン・トランザクションを再開する必要があります。

メイン・トランザクション内では、自律型トランザクションを開始する前にマークされたセーブポイントまでロールバックしても、自律型トランザクションはロールバックされません。自律型トランザクションは、メイン・トランザクションからは完全に独立していることに注意してください。

自律型トランザクションでのエラーの回避

一般的なエラーを回避するには、次のことに注意してください。

  • メイン・トランザクションが保持するリソースに、自律型トランザクションがアクセスしようとすると、デッドロックが発生することがあります。この場合は、データベースによって自律型トランザクションで例外が呼び出され、例外が未処理になった場合、自律型トランザクションはロールバックします。

  • データベース初期化パラメータTRANSACTIONSは、同時トランザクションの最大数を指定します。自律型トランザクションはメイン・トランザクションと同時に実行されるため、この最大数を超える場合があります。

  • コミットまたはロールバックせずにアクティブな自律型トランザクションを終了しようとすると、データベースによって例外が呼び出されます。例外が未処理になった場合、トランザクションはロールバックします。

  • 自律型トランザクションがオープンされている間は、自律型ルーチン内でPIPE ROW文を実行することはできません。PIPE ROW文を実行する前に、自律型トランザクションをクローズする必要があります。この処理は通常、PIPE ROW文を実行する前に自律型トランザクションをコミットまたはロールバックすることによって行います。

自律型トリガー

TCL文またはDDL文を実行する場合、トリガーは自律型である必要があります。

DDL文を実行する場合、トリガーは、システム固有の動的SQLを使用する必要があります。

関連項目:

トリガーの使用例の1つは、イベントのログを透過的に取ることです(たとえば、ある表に対するすべての挿入を、ロールバックしたものも含めてログに記録できます)。

例7-46 自律型トリガーによるINSERT文のログへの記録

この例では、EMPLOYEES表に行が挿入されるたびに、トリガーによって同じ行がログ表に挿入されます。トリガーは自律型であるため、メイン表への変更をコミットするかどうかに関係なく、ログ表への変更をコミットできます。

DROP TABLE emp;
CREATE TABLE emp AS SELECT * FROM employees;
 
-- Log table:
 
DROP TABLE log;
CREATE TABLE log (
  log_id   NUMBER(6),
  up_date  DATE,
  new_sal  NUMBER(8,2),
  old_sal  NUMBER(8,2)
);
 
-- Autonomous trigger on emp table:
 
CREATE OR REPLACE TRIGGER log_sal
  BEFORE UPDATE OF salary ON emp FOR EACH ROW
DECLARE
  PRAGMA AUTONOMOUS_TRANSACTION;
BEGIN
  INSERT INTO log (
    log_id,
    up_date,
    new_sal,
    old_sal
  )
  VALUES (
    :old.employee_id,
    SYSDATE,
    :new.salary,
    :old.salary
  );
  COMMIT;
END;
/
UPDATE emp
SET salary = salary * 1.05
WHERE employee_id = 115;
 
COMMIT;
 
UPDATE emp
SET salary = salary * 1.05
WHERE employee_id = 116;
 
ROLLBACK;
 
-- Show that both committed and rolled-back updates
-- add rows to log table
 
SELECT * FROM log
WHERE log_id = 115 OR log_id = 116;

結果:

    LOG_ID UP_DATE      NEW_SAL    OLD_SAL
---------- --------- ---------- ----------
       115 02-OCT-12    3255          3100
       116 02-OCT-12    3045          2900
 
2 rows selected.

例7-47 自律型トリガーによるDDLに対するシステム固有の動的SQLの使用

この例では、自律型トリガーでシステム固有の動的SQL(EXECUTE IMMEDIATE文)を使用し、行が表logに挿入された後で一時表を削除しています。

DROP TABLE temp;
CREATE TABLE temp (
  temp_id NUMBER(6),
  up_date DATE
);

CREATE OR REPLACE TRIGGER drop_temp_table
  AFTER INSERT ON log
DECLARE 
  PRAGMA AUTONOMOUS_TRANSACTION;
BEGIN
  EXECUTE IMMEDIATE 'DROP TABLE temp';
  COMMIT;
END;
/
-- Show how trigger works
SELECT * FROM temp;
 

結果:

no rows selected

INSERT INTO log (log_id, up_date, new_sal, old_sal)
VALUES (999, SYSDATE, 5000, 4500);
 
1 row created.
 
SELECT * FROM temp;
 

結果:

SELECT * FROM temp
              *
ERROR at line 1:
ORA-00942: table or view does not exist

SQLからの自律型ファンクションの起動

SQL文から呼び出されるファンクションは、副作用を制御するための規則に従う必要があります。

定義上は、自立型ルーチンによってデータベース状態の読取りおよび書込みは行われません(つまり、データベース表に対する問合せや変更が行われません)。

関連項目:

詳細は、「サブプログラムの副作用」を参照

例7-48 自律型ファンクションの起動

パッケージ・ファンクションlog_msgは自律型です。ファンクションが起動されると、データベース状態の書込み(データベース表の変更)禁止の規則に違反することなく、そのファンクションによってデータベース表debug_outputにメッセージが挿入されます。

DROP TABLE debug_output;
CREATE TABLE debug_output (message VARCHAR2(200));
 
CREATE OR REPLACE PACKAGE debugging AUTHID DEFINER AS
  FUNCTION log_msg (msg VARCHAR2) RETURN VARCHAR2;
END debugging;
/
CREATE OR REPLACE PACKAGE BODY debugging AS
  FUNCTION log_msg (msg VARCHAR2) RETURN VARCHAR2 IS
    PRAGMA AUTONOMOUS_TRANSACTION;
  BEGIN
    INSERT INTO debug_output (message) VALUES (msg);
    COMMIT;
    RETURN msg;
  END;
END debugging;
/
-- Invoke package function from query
DECLARE
  my_emp_id    NUMBER(6);
  my_last_name VARCHAR2(25);
  my_count     NUMBER;
BEGIN
  my_emp_id := 120;
 
  SELECT debugging.log_msg(last_name)
  INTO my_last_name
  FROM employees
  WHERE employee_id = my_emp_id;
 
  /* Even if you roll back in this scope,
     the insert into 'debug_output' remains committed,
     because it is part of an autonomous transaction. */
 
  ROLLBACK;
END;
/