XAトランザクションとは

XAトランザクションとは主に分散トランザクションを実現するために利用されるXAインターフェースに準じたトランザクションであり、 分散トランザクションを利用することで複数トランザクションに対してアトミック性(全てのトランザクションに対して一貫したcommit or rollbackを実現する)のある処理が可能となります。

XAインタフェースはORACLEが定めたものではなくThe Open Group(旧X/Open)という標準化団体が定義したもので、実質分散トランザクションのデファクトスタンダードとなっています。 ORACLEも含めメジャーなRDBMSはほぼXAトランザクションに対応しており、XAトランザクションを利用することでORACLEとDB2といった異種DB間をまたがるトランザクションを実現すること等が可能です。

XAトランザクションの用語

- TM(トランザクション・マネージャ)・・・分散トランザクションを管理する部分のプログラム。 トランザクションの開始やcommit、rollbackなどは全てTMが管理するためXAトランザクションの中にcommitや暗黙commitが発生するDDL文等を実装してはいけない。
- RM(リソース・マネージャ)・・・データ等資源を管理するプログラム。ORACLEデータベースもRMとなる。
- 2フェーズコミット・・・分散トランザクションにおいて準備(prepare) - 実行(commit or rollback)の2段階にわたってトランザクションの確定が行われる仕組み。 prepareとcommitの間で何らかの異常が起きた場合RM側ではcommitすべきかrollbackすべきか判断できないためインダウトトランザクションという不確定なトランザクションが残る場合があります。

XAトランザクションのサンプルプログラム

以下はPro*Cで実装したXAサンプルです。実行後何もキーを押さず30秒ほどしてタイムアウトが発生するとインダウトトランザクションになります。
//利用するテーブルDDL : create table tbl1(col1 number primary key); 
#include <stdio.h>
#include <xa.h>

EXEC SQL INCLUDE sqlca.h;

int main(void)
{
    //接続情報(ユーザ、パスワードtestでローカル接続)
    char *xa_connect_str ="Oracle_XA+Acc=P/test/test+NoLocal=T+SesTm=20+SesWt=10+LogDir=.+DbgFl=0x0";
    XID xidx = { 0x1e0a0a1e, 12, 8, "Branch1" };
    XID *xid = &xidx;
    int rmid = 1;
    int return_cd;

    printf("xa_sample start\n");

    //XAインターフェースを利用してORACLEに接続
    return_cd = xaoopen(xa_connect_str, rmid, TMNOFLAGS);
    if ( return_cd != 0) {
        printf("xaoopen error %d\n", return_cd);
        return 1;
    };

    //XAトランザクション開始
    return_cd = xaostart(xid, rmid, TMNOFLAGS);
    if ( return_cd != 0) {
        printf("xaostart error %d\n", return_cd);
        return 1;
    };

    //insert文実行
    exec sql insert into tbl1 values(1);
    if (sqlca.sqlcode != 0) {
        printf("sql error %d\n", sqlca.sqlcode);
        printf("%.*s\n", sqlca.sqlerrm.sqlerrml, sqlca.sqlerrm.sqlerrmc);
    }

    //XAトランザクション終了
    return_cd = xaoend(xid, rmid, TMSUCCESS);
    if ( return_cd != 0) {
        printf("xaoend error = %d\n", return_cd);
        return 1;
    };

    //commit準備
    return_cd = xaoprepare(xid, rmid, 0x00);
    if ( return_cd != 0) {
        printf("xaoprepare error = %d\n", return_cd);
        return 1;
    };
    printf("press enter key\n");
    //キーを押すまで待機(タイムアウト時間を過ぎるとインダウトトランザクション化する)
    getchar();

    //コミット
    return_cd = xaocommit(xid, rmid, 0x00);
    if ( return_cd != 0) {
        printf("xaocommit error = %d\n", return_cd);
        return 1;
    };

    //接続クローズ
    return_cd = xaoclose(xa_connect_str, rmid, TMNOFLAGS);
    if ( return_cd != 0) {
        printf("xaoclose error = %d\n", return_cd);
        return 1;
    };
    printf("xa_sample end\n");
    return 0;
}
コンパイルは以下のようにします。(demo_proc.mkがない場合はデモプログラムがインストールされていませんのでインストールが必要です)
[ora102@linux1 tmp]$ make -f $ORACLE_HOME/precomp/demo/proc/demo_proc.mk build EXE=xa_sample OBJS=xa_sample.o
make -f /app/oracle/product/10.2.0/db_1/precomp/demo/proc/demo_proc.mk PROCFLAGS="" PCCSRC=xa_sample I_SYM=include= pc1
make[1]: ディレクトリ `/var/tmp' に入ります
proc  iname=xa_sample include=. include=/app/oracle/product/10.2.0/db_1/precomp/public include=/app/oracle/product/10.2.0/db_1/rdbms/public 
include=/app/oracle/product/10.2.0/db_1/rdbms/demo include=/app/oracle/product/10.2.0/db_1/plsql/public include=/app/oracle/product/10.2.0/db_1/network/public

Pro*C/C++: Release 10.2.0.4.0 - Production on 金 10月 12 19:42:09 2012

Copyright (c) 1982, 2007, Oracle.  All rights reserved.

システムのデフォルト・オプション値: /app/oracle/product/10.2.0/db_1/precomp/admin/pcscfg.cfg

make[1]: ディレクトリ `/var/tmp' から出ます
/usr/bin/gcc  -O2   -fPIC -DPRECOMP -I. -I/app/oracle/product/10.2.0/db_1/precomp/public -I/app/oracle/product/10.2.0/db_1/rdbms/public 
 -I/app/oracle/product/10.2.0/db_1/rdbms/demo -I/app/oracle/product/10.2.0/db_1/plsql/public -I/app/oracle/product/10.2.0/db_1/network/public
 -DLINUX -D_GNU_SOURCE -D_LARGEFILE64_SOURCE=1 -D_LARGEFILE_SOURCE=1 -DSLTS_ENABLE -DSLMXMX_ENABLE -D_REENTRANT -DNS_THREADS    -c xa_sample.c
/usr/bin/gcc -o xa_sample xa_sample.o -L/app/oracle/product/10.2.0/db_1/lib/ -lclntsh `cat /app/oracle/product/10.2.0/db_1/lib/ldflags`
   `cat /app/oracle/product/10.2.0/db_1/lib/sysliblist` -ldl -lm

インダウトトランザクションの解決

ペンディング中のトランザクションや、インダウトトランザクションがある場合以下のように関連ディクショナリに情報が表示されるようになります。
SQL> select * from dba_2pc_pending;

LOCAL_TRAN_ID
------------------------------------------------------------------
GLOBAL_TRAN_ID
----------------------------------------------------------------------------------------------------------------------------------------------
----------------------------------------------------------
STATE                                            MIXED     ADV
------------------------------------------------ --------- ---
TRAN_COMMENT
----------------------------------------------------------------------------------------------------------------------------------------------
----------------------------------------------------------
FAIL_TIM FORCE_TI RETRY_TI
-------- -------- --------
OS_USER
----------------------------------------------------------------------------------------------------------------------------------------------
--------------------------------------------------
OS_TERMINAL
----------------------------------------------------------------------------------------------------------------------------------------------
----------------------------------------------------------
HOST
----------------------------------------------------------------------------------------------------------------------------------------------
----------------------------------------------------------
DB_USER                                                                                    COMMIT#
------------------------------------------------------------------------------------------ ------------------------------------------------
9.29.65022
503974430.4272616E6368310000000000
prepared                                         no

07:25:47          07:25:47
ora102
pts/1
linux1

SQL> select * from dba_pending_transactions;

  FORMATID GLOBALID
---------- --------------------------------------------------------------------------------------------------------------------------------
BRANCHID
--------------------------------------------------------------------------------------------------------------------------------
 503974430 4272616E6368310000000000
0000000000000000
また、インダウトトランザクションが保持しているロックで競合した場合ロック獲得の待機はせず以下のようなエラーが即発生します。
SQL> insert into tbl1 values(1);
insert into tbl1 values(1)
            *
行1でエラーが発生しました。:
ORA-01591: インダウト分散トランザクション9.29.65022がロックを保持しています。
インダウトトランザクションを解決するにはforceオプション付でcommiet/rollbackを実行します。 また、残ったトランザクション情報はdbms_transactionパッケージで消去することができます。
SQL> commit force '9.29.65022';

コミットが完了しました。

SQL> execute dbms_transaction.PURGE_LOST_DB_ENTRY('9.29.65022');

PL/SQLプロシージャが正常に完了しました。
★ORACLE案件承ります