|
グローバル・トランザクションとは、複数のリソース・マネージャを使用し、複数のサーバー上で行われる複数の操作を1つの論理単位として処理できるようにするメカニズムです。
プロセスがトランザクション・モードになると、サーバーにリクエストされたサービスが現在のトランザクションにかわって処理されます。呼び出されてトランザクションに参加したサービスは、「トランザクションの参加リソース」と呼ばれます。参加リソースから返される値によって、トランザクションの結果が変わる場合があります。
グローバル・トランザクションは複数のローカル・トランザクションから構成され、各トランザクションは同じリソース・マネージャにアクセスします。リソース・マネージャは、同時実行性制御とデータ更新の原子性を実現します。ローカル・トランザクションでは、アクセスが正常に終了するか、または全体が失敗します。つまり、一部だけが成功することはありません。
1つのトランザクションに参加可能なサーバー・グループは最大16個です。
Oracle Tuxedoシステムでは、グローバル・トランザクションが参加しているリソース・マネージャと共に管理され、原子性、一貫性、独立性、および持続性という特徴を持つ特定シーケンスの操作として処理されます。つまり、グローバル・トランザクションは、以下のような特徴を持つ論理的な作業単位と言えます。
Oracle Tuxedoシステムでは、個々のグローバル・トランザクションのステータスがトラッキングされ、そのトランザクションをコミットするかロールバックするかが決定されます。
| 注: | トランザクションで、flags引数に明示的にTPNOTRANを設定してtpcall()、tpacall()またはtpconnect()を呼び出した場合、呼び出されたサービスによって実行される操作はトランザクションに含まれません。その場合、呼出し側プロセスは、呼び出されたサービスを現在のトランザクションの参加リソースとは見なしません。結果として、呼び出されたプロセスによって実行されたサービスは、現在のトランザクションの結果からの影響を受けません。XA準拠のサーバー・グループのサービスに対する呼出しにTPNOTRANが設定されている場合、その呼出しはトランザクション・モード外、または別のトランザクションで実行されます。どちらで実行されるかは、サービスの設定方法とコーディング方法によって決まります。詳細については、「グローバル・トランザクションの暗黙的な定義」を参照してください。 |
グローバル・トランザクションを開始するには、次のシグネチャを使用してtpbegin(3c)関数を呼び出します。
int
tpbegin(unsigned long timeout,longflags)
表9-1は、tpbegin()関数の引数を示しています。
tpbegin()は、どのプロセスからも呼び出すことができます。ただし、既にトランザクション・モードになっているプロセス、または未処理の応答を待っているプロセスからは呼び出すことができません。トランザクション・モードでtpbegin()が呼び出されると、プロトコル・エラーになって呼出しが失敗し、tperrno(5)にTPEPROTOが設定されます。プロセスがトランザクション・モードの場合でも、この失敗はトランザクションには影響しません。
リスト9-1は、グローバル・トランザクションの定義方法の概要を示しています。
. . .
if (tpbegin(timeout,flags) == -1)
error routine
program statements
. . .
if (tpcommit(flags) == -1)
error routine
リスト9-2は、トランザクションの定義方法をより詳細に示しています。このサンプル・コードは、Oracle Tuxedoシステムで提供される銀行業務のサンプル・アプリケーション bankappのaudit.cクライアント・プログラムから引用したものです。
#include <stdio.h> /* UNIX */
#include <string.h> /* UNIX */
#include <atmi.h> /* BEA Tuxedo System */
#include <Uunix.h> /* BEA Tuxedo System */
#include <userlog.h> /* BEA Tuxedo System */
#include "bank.h" /* BANKING #defines */
#include "aud.h" /* BANKING view defines */
#define INVI 0 /* account inquiry */
#define ACCT 1 /* account inquiry */
#define TELL 2 /* teller inquiry */
static int sum_bal _((char *, char *));
static long sitelist[NSITE] = SITEREP; /* list of machines to audit */
static char pgmname[STATLEN]; /* program name = argv[0] */
static char result_str[STATLEN]; /* string to hold results of query */
main(argc, argv)
int argc;
char *argv[];
{
int aud_type=INVI; /* audit type -- invalid unless specified */
int clarg; /* command line arg index from optind */
int c; /* Option character */
int cflgs=0; /* Commit flags, currently unused */
int aflgs=0; /* Abort flags, currently unused */
int nbl=0; /* count of branch list entries */
char svc_name[NAMELEN]; /* service name */
char hdr_type[NAMELEN]; /* heading to appear on output */
int retc; /* return value of sum_bal() */
struct aud *audv; /* pointer to audit buf struct */
int audrl=0; /* audit return length */
long q_branchid; /* branch_id to query */
. . . /* Get Command Line Options and Set Variables */
/* Join application */
if (tpinit((TPINIT *) NULL) == -1) {
(void)userlog("%s: failed to join application\n", pgmname);
exit(1);
}
/* Start global transaction */
if (tpbegin(30, 0) == -1) {
(void)userlog("%s: failed to begin transaction\n", pgmname);
(void)tpterm();
exit(1);
}
if (nbl == 0) { /* no branch id specified so do a global sum */
retc = sum_bal(svc_name, hdr_type); /* sum_bal routine not shown */
} else {
/* Create buffer and set data pointer */
if ((audv = (struct aud *)tpalloc("VIEW", "aud", sizeof(struct aud)))
== (struct aud *)NULL) {
(void)userlog("audit: unable to allocate space for VIEW\n");
exit(1);
}
/* Prepare aud structure */
audv->b_id = q_branchid;
audv->balance = 0.0;
audv->ermsg[0] = '\0';
/* Do tpcall */
if (tpcall(svc_name,(char *)audv,sizeof(struct aud),
(char **)audv,(long *)audrl,0) == -1){
(void)fprintf (stderr,"%s service failed\n%s: %s\n",
svc_name, svc_name, audv->ermsg);
retc = -1;
}else {
(void)sprintf(result_str,"Branch %ld %s balance is $%.2f\n",
audv->b_id, hdr_type, audv->balance);
}
tpfree((char *)audv);
}
/* Commit global transaction */
if (retc < 0) /* sum_bal failed so abort */
(void) tpabort(aflgs);
else {
if (tpcommit(cflgs) == -1) {
(void)userlog("%s: failed to commit transaction\n", pgmname);
(void)tpterm();
exit(1);
}
/*print out results only when transaction has committed successfully*/
(void)printf("%s",result_str);
}
/* Leave application */
if (tpterm() == -1) {
(void)userlog("%s: failed to leave application\n", pgmname);
exit(1);
}
トランザクションがタイムアウトになった場合、tpcommit()を呼び出すと、トランザクションが中断します。その結果、tpcommit()が失敗し、tperrno(5)にTPEABORTが設定されます。
リスト9-3は、トランザクションのタイムアウトをテストする方法を示しています。timeoutの値が30秒に設定されていることに注目してください。
if (tpbegin(30, 0) == -1) {
(void)userlog("%s: failed to begin transaction\n", argv[0]);
tpterm();
exit(1);
}
. . .
communication calls
. . .
if (tperrno == TPETIME){
if (tpabort(0) == -1) {
check for errors;
}
else if (tpcommit(0) == -1){
check for errors;
}
. . .| 注: | トランザクション・モードのプロセスで、flags引数にTPNOTRANを設定して通信呼出しを行うと、呼び出されたサービスは現在のトランザクションに参加できません。サービス・リクエストの成功や失敗は、トランザクションの結果に影響しません。トランザクションは、サービスがトランザクションに参加しているかどうかには関係なく、そのサービスから応答が返されるのを待つ間にタイムアウトになる場合もあります。TPNOTRANフラグの影響については、「エラーの管理」を参照してください。 |
場合によっては、実行中のトランザクションから一時的にプロセスを削除し、tpbegin()またはtpresume()を呼び出して、そのプロセスで別のトランザクションを開始した方がよい場合もあります。たとえば、サーバーがデータベースの中央イベント・ログにリクエストを記録する場合、トランザクションが中断してもログ処理をロールバックしたくない場合などです。
Oracle Tuxedoシステムでは、このような場合にクライアントまたはサーバーでトランザクションを中断して再開するtpsuspend(3c)とtpresume(3c)という2つの関数が提供されています。この2つの関数を使用すると、次の処理を行うことができます。
tpsuspend(3c)関数を使用すると、現在のトランザクションを中断できます。tpsuspend()関数の呼出しには、次のシグネチャを使用します。
int
tpsuspend(TPTRANID *t_id,long flags)
表9-2は、tpsuspend()関数の引数を示しています。
未処理の非同期イベントを持つトランザクションを中断することはできません。トランザクションが中断すると、トランザクションがコミットまたは中断されるまで、あるいはタイムアウトになるまで、中断前に行った変更はすべて保留状態のまま維持されます。
現在のトランザクションを再開するには、次のシグネチャを使用してtpresume(3c)関数を呼び出します。
int
tpresume(TPTRANID *t_id,long flags)
表9-3は、tpresume()関数の引数を示しています。
トランザクションを中断したプロセス以外のプロセスからトランザクションを再開できますが、制限があります。この制限については、『Oracle Tuxedo ATMI C言語関数リファレンス』の「tpsuspend(3c)」と「tpresume(3c)」を参照してください。
リスト9-4は、あるトランザクションを中断し、別のトランザクションを開始してコミットし、最初のトランザクションを再開する方法を示しています。コードを簡単にするために、エラー・チェック・コードは省略してあります。
DEBIT(SVCINFO *s)
{
TPTRANID t;
tpsuspend(&t,TPNOFLAGS); /* suspend invoking transaction*/
tpbegin(30,TPNOFLAGS); /* begin separate transaction */
Perform work in the separate transaction.tpcommit(TPNOFLAGS); /* commit separate transaction */
tpresume(&t,TPNOFLAGS); /* resume invoking transaction*/
.
.
.
tpreturn(. . . );
}
グローバル・トランザクションを終了するには、tpcommit(3c)を呼び出して現在のトランザクションをコミットするか、またはtpabort(3c)を呼び出して処理を中断して、すべての操作をロールバックします。
| 注: | tpcall()、tpacall()、またはtpconnect()を呼び出すときにflags引数に明示的にTPNOTRANが設定されている場合、呼び出されたサービスによって実行される操作は、トランザクションに含まれません。つまり、このようなサービスによって実行される操作は、tpabort()関数を呼び出したときにロールバックされません。 |
tpcommit(3c)関数は、現在のトランザクションをコミットします。tpcommit()関数から正常に制御が戻ると、現在のトランザクションの結果としてリソースに加えられた変更は永続的なものとなります。
tpcommit()関数の呼出しには、次のシグネチャを使用します。
int
tpcommit(long flags)
flags引数は現在使用されていませんが、今後のリリースとの互換性を保つためにゼロに設定してください。
tpcommit()を正常に実行するには、次の条件を満たしていることが必要です。
TPNOTRANフラグを設定しないで呼び出した場合、呼出し側プロセスに未処理のトランザクション応答が存在することはできません。 最初の条件を満たしていない場合、呼出しは失敗し、プロトコル・エラーを示すTPEPROTOがtperrno(5)に設定されます。2番目または3番目の条件を満たしていない場合、呼出しは失敗し、トランザクションがロールバックされたことを示すTPEABORTがtperrno()に設定されます。トランザクションに未処理の応答があるときにtpcommit()がイニシエータによって呼び出されると、トランザクションは中断され、トランザクションに関連する応答記述子が無効になります。参加リソースがtpcommit()またはtpabort()を呼び出しても、トランザクションには影響しません。
サービス呼出しでTPFAILが戻されるか、またはサービス・エラーが発生すると、トランザクションはロールバックのみの状態になります。「ロールバックのみ」のトランザクションに対してtpcommit()が呼び出されると、この関数はトランザクションを取り消し、-1を返してtperrno(5)にTPEABORTを設定します。すでにタイムアウトになっているトランザクションに対してtpcommit()を呼び出した場合も同じ結果になり、tpcommit()は -1を返してtperrno()にTPEABORTが設定されます。トランザクション・エラーの詳細は、「エラーの管理」を参照してください。
tpcommit()関数が呼び出されると、2フェーズ・コミット・プロトコルによる通信が開始されます。このプロトコルは、その名前が示すように、次の2段階の処理に分かれています。
トランザクションのイニシエータがtpcommit()関数を呼び出すと、コミット・シーケンスが開始されます。指定されたコーディネータ・グループ内のOracle Tuxedo TMSサーバー・プロセスは、コミット・プロトコルの最初のフェーズを実行する各参加リソース・グループのTMSと通信を行います。次に、各グループのTMSは、そのグループのリソース・マネージャ(RM)に、トランザクション・マネージャとRM間の通信用に定義されているXAプロトコルを使用してコミットするように指示します。RMは、安定記憶域にコミット・シーケンスの前後のトランザクションの状態を書き込み、TMSに成功か失敗かを通知します。その後、TMSはトランザクション・コーディネータのTMSにレスポンスを渡します。
トランザクション・コーディネータのTMSは、すべてのグループから成功の通知を受け取ると、トランザクションのコミット中であることをログに記録し、第2フェーズのコミット通知をすべての参加リソース・グループに送信します。その後、各グループのRMはトランザクションの更新を完了します。
トランザクション・コーディネータのTMSが、グループから第1フェーズのコミットの失敗の通知を受けた場合、またはグループからの応答の受信に失敗した場合、各RMにロールバック通知を送信し、RMはすべてのトランザクション更新を以前の状態に戻します。これにより、tpcommit()は失敗し、tperrno(5)にTPEABORTが設定されます。
1つのトランザクションに複数のグループが関係している場合、tpcommit()が正常に制御を戻すための条件として、次のいずれかを指定できます。
この2つの条件のいずれかを指定するには、構成ファイルのRESOURCESセクションのCMTRETパラメータに、次のいずれかの値を設定します。
デフォルトでは、CMTRETはCOMPLETEに設定されます。
後で構成ファイルの設定値を変更する場合は、flags引数にTP_CMT_LOGGEDまたはTP_CMT_COMPLETEを設定して、tpscmt()を呼び出します。
ほとんどの場合、グローバル・トランザクションのすべての参加リソースが第1フェーズの正常終了を記録した場合、第2フェーズも正常終了します。CMTRETにLOGGEDを設定すると、tpcommit()の呼出しから制御が多少早く戻るようになります。ただし、参加リソースが、コミットの決定と矛盾する方法で、トランザクションの担当部分をヒューリスティックに終了する危険性があります。
このようなリスクを負うべきかどうかの選択は、アプリケーションの性質に左右されます。たとえば、財務アプリケーションなど正確さが要求されるアプリケーションでは、すべての参加リソースが2フェーズ・コミットを完了するまでは、制御を戻さないようにします。時間的な条件を重視するアプリケーションでは、正確さを犠牲にしても実行速度を上げます。
tpabort(3c)関数を使用すると、異常な状態を通知して、明示的にトランザクションを中断できます。この関数は、トランザクションの応答に未処理のものがあると、その呼出し記述子を無効にします。その場合、トランザクションで行われた変更はリソースには適用されません。tpabort()関数の呼出しには、次のシグネチャを使用します。
int
tpabort(long flags)
flags引数は現在使用されていませんが、今後のリリースとの互換性を保つためにゼロに設定してください。
図9-1は、グローバル・トランザクションを行う階層構造の会話型接続を示しています。

tpbegin()とtpconnect()を呼び出して、トランザクション・モードで接続を開始します。TPEV_SVCSUCCまたはTPEV_SVCFAIL)を示す応答を階層構造を通じてトランザクションを開始したプロセスに送信します。この例では、トランザクションを開始したプロセスはクライアント(プロセスA)です。従属サービスは、応答の送信が終了すると、つまり未処理の応答がなくなると、tpreturn()を呼び出します。tpcommit()は成功しないので、クライアントはtpabort()を呼び出します。 リスト9-5では、クライアントはREPORTサービスへの同期呼出し(18行目)を行います。次に、通信呼出しで戻される可能性があるエラーを調べて(19 - 34行目)、参加リソースの失敗を確認します。
001 #include <stdio.h>
002 #include "atmi.h"
003
004 main()
005 {
006 char *sbuf, *rbuf;
007 long slen, rlen;
008 if (tpinit((TPINIT *) NULL) == -1)
009 error message, exit program;
010 if (tpbegin(30, 0) == -1)
011 error message, tpterm, exit program;
012 if ((sbuf=tpalloc("STRING", NULL, 100)) == NULL)
013 error message, tpabort, tpterm, exit program;
014 if ((rbuf=tpalloc("STRING", NULL, 2000)) == NULL)
015 error message, tpfree sbuf, tpabort, tpterm, exit program;
016 (void)strcpy(sbuf, "REPORT=accrcv DBNAME=accounts");
017 slen=strlen(sbuf);
018 if (tpcall("REPORT", sbuf, slen, &rbuf, &rlen, 0) == -1) {
019 switch(tperrno) {
020 case TPESVCERR:
021 fprintf(stderr,
022 "REPORT service's tpreturn encountered problems\n");
023 break;
024 case TPESVCFAIL:
025 fprintf(stderr,
026 "REPORT service TPFAILED with return code of %d\n", tpurcode);
027 break;
028 case TPEOTYPE:
029 fprintf(stderr,
030 "REPORT service's reply is not of any known data type\n");
031 break;
032 default:
033 fprintf(stderr,
034 "REPORT service failed with error %d\n", tperrno);
035 break;
036 }
037 if (tpabort(0) == -1){
038 check for errors;
039 }
040 }
041 else
042 if (tpcommit(0) == -1)
043 fprintf(stderr, "Transaction failed at commit time\n");
044 tpfree(rbuf);
045 tpfree(sbuf);
046 tpterm();
047 exit(0);
048 }
アプリケーションでは、次のいずれかの方法でグローバル・トランザクションを開始できます。
構成ファイルのシステム・パラメータAUTOTRANを設定すると、サービス・ルーチンがトランザクション・モードになります。AUTOTRANにYを設定すると、別のプロセスからリクエストを受信したときに、サービス・サブルーチン内でトランザクションが自動的に開始されます。
暗黙的にトランザクションを定義する場合は、以下の規則に従います。
AUTOTRANがトランザクションを開始するように設定されていると、プロセスが別のプロセスのサービスをリクエストしたときにトランザクションが開始されます。flagsパラメータがTPNOTRANに設定されているかどうかをまず確認します。 flags引数にTPNOTRANが設定されていない場合、呼び出されたプロセスは伝達の規則によってトランザクション・モードになります。システムによってAUTOTRANパラメータは確認されません。
flags引数にTPNOTRANが設定されている場合、呼び出されたプロセスによって実行されるサービスは、現在のトランザクションに含まれません。つまり、「伝達の規則」は適用されません。システムによってAUTOTRANパラメータが確認されます。
| 注: | サービスは自動的にトランザクション・モードにできるので、TPNOTRANフラグが設定されたサービスから、AUTOTRANパラメータが設定されたサービスを呼び出すことができます。そのようなサービスが別のサービスをリクエストした場合、サービスのデータ構造体のflagsメンバーは問合せを実行したときにTPTRANを返します。たとえば、flagsにTPNOTRAN | TPNOREPLYを設定して呼出しを行い、サービスが呼び出されたときに(そのサービスによって)トランザクションが自動的に開始された場合、データ構造体のflagsメンバーは、TPTRAN | TPNOREPLYに設定されます。 |
アプリケーション・プログラマがXA準拠のサーバー・グループのサービスをコーディングする場合、グループのリソース・マネージャを介して操作を行うようにするのが一般的です。通常、サービスは1つのトランザクション内ですべての操作を行います。それに対して、flagsにTPNOTRANを設定してサービスを呼び出すと、データベース操作の実行時に予期しない結果を受け取る場合があります。
予測不能な動作を防ぐには、XA準拠のリソース・マネージャに関連付けられているグループのサービスが、常にトランザクション・モードで呼び出されるようにアプリケーションを設計します。または、構成ファイルのAUTOTRANにYを設定します。また、サービス・コードの早い段階で、トランザクション・レベルを確認します。
トランザクション・モードのプロセスが別のプロセスのサービスをリクエストした場合、後者のプロセスは不参加の指示が特にないかぎり、そのトランザクションの参加リソースになります。
特定のエラー条件を回避したり正しく解釈するには、プロセスがトランザクション・モードかどうかを確認することが大切です。たとえば、すでにトランザクション・モードになっているプロセスがtpbegin()を呼び出すとエラーになります。そのようなプロセスがtpbegin()を呼び出すと、呼出しは失敗し、tperrno(5)にTPEPROTOが設定されて、呼出し側がすでにトランザクションに参加しているにもかかわらず呼び出されたことが示されます。トランザクションに影響はありません。
サービス・サブルーチンがトランザクション・モードかどうかを確認した後で、tpbegin()を呼び出すようにアプリケーションを設計できます。次のいずれかの方法で、トランザクション・レベルを確認できます。
flagsフィールドに対して問合せを実行します。TPTRANに設定されていると、サービスはトランザクション・モードになっています。 tpgetlev()関数の呼出しには、次のシグネチャを使用します。
int
tpgetlev() /* Get current transaction level */
tpgetlev()関数に引数は必要ありません。この関数は、呼出し側がトランザクション・モードになっていない場合は0を返し、トランザクション・モードになっている場合は1を返します。
リスト9-6は、OPEN_ACCTサービスの1つであり、tpgetlev()関数(12行目)を使用してトランザクション・レベルを確認する方法を示しています。プロセスがトランザクション・モードになっていない場合、アプリケーションでトランザクションを開始します(14行目)。tpbegin()が失敗した場合、メッセージがステータス行に戻され(16行目)、tpreturn()のrcode引数にグローバル変数tpurcode(5)で取得できるコードが設定されます(1行目と17行目)。
001 #define BEGFAIL 3 /* tpurcode setting for return if tpbegin fails */
002 void
003 OPEN_ACCT(transb)
004 TPSVCINFO *transb;
005 {
... other declarations ...
006 FBFR *transf; /* fielded buffer of decoded message */
007 int dotran; /* checks whether service tpbegin/tpcommit/tpaborts */
008 /* set pointer to TPSVCINFO data buffer */
009 transf = (FBFR *)transb->data;
010 /* Test if transaction exists; initiate if no, check if yes */
011 dotran = 0;
012 if (tpgetlev() == 0) {
013 dotran = 1;
014 if (tpbegin(30, 0) == -1) {
015 Fchg(transf, STATLIN, 0,
016 "Attempt to tpbegin within service routine failed\n");
017 tpreturn(TPFAIL, BEGFAIL, transb->data, 0, 0);
018 }
019 }
. . .
AUTOTRANにYが設定されている場合、トランザクション関数のtpbegin()、tpcommit()、tpabort()を明示的に呼び出す必要はありません。その結果、トランザクション・レベルを確認するオーバーヘッドを減らすことができます。また、TRANTIMEパラメータを設定して、タイムアウト間隔を指定することもできます。タイムアウト間隔は、サービスに対するトランザクションが開始されてからの経過時間です。また、トランザクションが完了しなかった場合は、トランザクションがロールバックされるまでの時間です。
たとえば、前述のコードのOPEN_ACCTサービスを変更するとします。現在のコードでは、OPEN_ACCTにトランザクションが明示的に定義され、そのトランザクションの有無を確認しています(7行目、10 - 19行目)。これらの処理のオーバーヘッドを減らすには、そのコードを削除します。その場合、OPEN_ACCTは常にトランザクション・モードで呼び出す必要があります。この要件を指定するには、構成ファイルのAUTOTRANとTRANTIMEシステム・パラメータを有効にします。
AUTOTRAN構成パラメータ TRANTIME構成パラメータ
|