この章では、基本的なデータベースの概要と、トランザクション処理の実行方法について説明します。Oracleデータの変更内容の確定または取消しを制御する方法など、データベースの整合性を維持するための基本的な技術を学習します。
この章の項目は、次のとおりです。
各項で、CONNECT文の詳細な構文について説明します。構文は次のとおりです。
EXEC SQL CONNECT { :user IDENTIFIED BY :oldpswd | :usr_psw } [[ AT { dbname | :host_variable }] USING :connect_string ] [ {ALTER AUTHORIZATION :newpswd | IN { SYSDBA | SYSOPER } MODE} ] ;
データの問合せまたは操作をする前に、Pro*C/C++プログラムをデータベースに接続する必要があります。ログインするには、単にCONNECT文を使用します。
EXEC SQL CONNECT :username IDENTIFIED BY :password ;
usernameおよびpasswordは、charまたはVARCHARホスト変数です。
または、この文は次のようにも指定できます。
EXEC SQL CONNECT :usr_pwd;
ホスト変数usr_pwdには、スラッシュ文字(/)で区切られたユーザー名とパスワードが含まれます。
これらは、CONNECT文の簡略化されたサブセットです。
CONNECT文は、プログラムで実行される最初のSQL文にする必要があります。つまり、プリコンパイル・ユニット内では、他のSQL文をCONNECT文の前に物理的に置くことはできますが、論理的に置くことはできません。
Oracleのユーザー名とパスワードを別々に入力するには、2つのホスト変数を文字列またはVARCHARとして定義します。(ユーザー名とパスワードの両方を含むユーザー名を入力する場合、必要なホスト変数は1つのみです。)
CONNECTを実行する前に、ユーザー名とパスワードの変数を設定する必要があります。設定しない場合、CONNECTは失敗します。プログラムのプロンプトで値の入力を求めることも、次のように値をハードコードすることもできます。
char *username = "SCOTT"; char *password = "TIGER"; ... EXEC SQL WHENEVER SQLERROR ... EXEC SQL CONNECT :username IDENTIFIED BY :password;
ただし、ユーザー名とパスワードは、CONNECT文にはハードコードできません。また、引用符で囲んだリテラルは使用できません。たとえば、次の2つの文はどちらも無効です。
EXEC SQL CONNECT SCOTT IDENTIFIED BY TIGER; EXEC SQL CONNECT 'SCOTT' IDENTIFIED BY 'TIGER';
ユーザー名およびパスワードをハードコードすることはお薦めしません。
Pro*C/C++のクライアント・アプリケーションでは、EXEC SQL CONNECT文を拡張し、実行時にユーザーのパスワードを変更できます。
この項では、様々なALTER AUTHORIZATION句の実行結果について説明します。
アプリケーションで次の文が発行されるとします。
EXEC SQL CONNECT ..; /* No ALTER AUTHORIZATION clause */
通常の接続試行が実行されます。この場合、想定される結果は次のとおりです。
アプリケーションは問題なく接続できる。
アプリケーションは接続できるが、パスワードに関する警告を受ける。この警告は、パスワードの期限は切れているが、まだログインできる猶予期間であることを示しています。この時点で、ユーザーは、アカウントがロックされる前にパスワードを変更するように求められます。
アプリケーションは接続できない。この場合、次の原因が考えられます。
パスワードが間違っています。
アカウントが期限切れになっているか、ロック状態になっています。
次のCONNECT文があるとします。
EXEC SQL CONNECT .. ALTER AUTHORIZATION :newpswd;
この文は、アプリケーションがアカウントのパスワードをnewpswd
で指定した値に変更することを示します。変更後は、user
/newpswd
で接続試行が実行されます。この場合、想定される結果は次のとおりです。
アプリケーションは問題なく接続できる。
アプリケーションは接続できない。次のどちらかの原因が考えられます。
なんらかの理由でパスワードを検証できませんでした。この場合、パスワードは変更されません。
アカウントがロックされています。パスワードは変更できません。
Oracle Netドライバを使用して接続するには、tnsnames.ora
構成ファイルまたはOracle Namesで定義されているサービス名を使用します。
Oracle Namesを使用する場合、ネーム・サーバーは、ネットワーク定義データベースからサービス名を取得します。
注意: SQL*Net V1はOracle8では動作しません。 |
Oracle Netの詳細は、『Oracle Net Services管理者ガイド』を参照してください。
CLUSTER$username
usernameは現行のオペレーティング・システムのユーザー名、CLUSTER$usernameは有効なOracleデータベース・ユーザー名です。(CLUSTER$の実際の値は、INIT.ORAパラメータ・ファイルに定義されています。)Pro*C/C++プリコンパイラに渡すのは、スラッシュ文字のみです。次に例を示します。
... char *oracleid = "/"; ... EXEC SQL CONNECT :oracleid;
これにより、ユーザーCLUSTER$usernameで自動的に接続されます。たとえば、オペレーティング・システムのユーザー名がRHILLで、CLUSTER$RHILLが有効なOracleユーザー名の場合は、/を使用した接続により、ユーザーCLUSTER$RHILLでOracleに自動的にログインできます。
また、プリコンパイラには文字列内で/を渡すこともできます。ただし、その文字列に後続の空白を含めることはできません。たとえば、次のCONNECT文は失敗します。
... char oracleid[10] = "/ "; ... EXEC SQL CONNECT :oracleid;
AUTO_CONNECT=YESで、最初の実行SQL文を処理するときにアプリケーションがまだデータベースに接続されていない場合、次のユーザーIDを使用して接続が試行されます。
CLUSTER$<username>
usernameは現行のオペレーティング・システムのユーザー名またはタスク名、CLUSTER$usernameは有効なOracleユーザーIDです。AUTO_CONNECTのデフォルト値はNOです。
AUTO_CONNECT=NOの場合、Oracleに接続するにはプログラムでCONNECT文を使用する必要があります。
Oracle8iより前のリリースでは、SYSOPERまたはSYSDBAシステム権限を取得するためにここで説明するような句を使用する必要はありませんが、Oracle8i以上では使用する必要があります。
SYSDBAまたはSYSOPERシステム権限でログインするには、次のオプション文字列を他のすべての句の後に追加します。
[IN { SYSDBA | SYSOPER } MODE]
次に例を示します。
EXEC SQL CONNECT ... IN SYSDBA MODE ;
このオプションに適用される制限事項は、次のとおりです。
プリコンパイラのAUTO_CONNECT=YESオプション設定を使用する場合、このオプションは使用できません。
CONNECT文にALTER AUTHORIZATIONキーワードを使用している場合、このオプションは使用できません。
この項では、高度な接続で使用できるオプションについて説明します。
ネットワーク上の通信ポイントは、ノードと呼ばれます。Oracle Netでは、ネットワーク上のノード間で情報(SQL文、データおよびステータス・コード)を送信できます。
プロトコルは、ネットワークへのアクセスに関する一連の規則です。この規則では、障害後のリカバリ手順、データの転送およびエラー検査のフォーマットなどが規定されます。
ローカル・ドメイン内のデフォルトのデータベースに接続するためにOracle Netの構文で使用するのは、そのデータベースのサービス名のみです。
サービス名がデフォルト(ローカル)・ドメイン内にない場合は、グローバル指定(すべてのドメインの指定)を使用する必要があります。次に例を示します。
HR.US.ORACLE.COM
Pro*C/C++では、Oracle Net経由で分散処理がサポートされます。アプリケーションでは、ローカル・データベースとリモート・データベースの任意の組合せに同時にアクセスしたり、同一データベースへの複数の接続を確立したりできます。図3-1では、アプリケーション・プログラムはOracleの1つのローカル・データベースおよび3つのリモート・データベースと接続しています。ORA2、ORA3およびORA4は、単にCONNECT文に使用される論理名です。
ネットワーク上で異なるマシンおよびオペレーティング・システム間の境界を排除することで、Oracle NetはOracleツール製品に分散処理環境を提供します。この項では、Pro*C/C++によりOracle Net経由で分散処理がサポートされる方法について説明します。アプリケーションから可能な操作は、次のとおりです。
他のデータベースへの直接または間接アクセス
ローカル・データベースとリモート・データベースの任意の組合せへの同時アクセス
同一データベースへの複数接続
Oracle Netのインストール方法および使用可能なデータベースの識別方法は、『Oracle Database Net Services管理者ガイド』およびシステム固有のOracleドキュメントを参照してください。
各ノードにはデフォルトのデータベースがあります。CONNECT文でデータベース名のみを指定し、ドメインを指定しない場合、指定したローカル・ノードまたはリモート・ノード上のデフォルトのデータベースに接続されます。
デフォルトの接続は、AT句のないCONNECT文によって行われます。ローカルまたはリモート・ノード上のデフォルトまたは非デフォルトのデータベースに接続できます。AT句のないSQL文は、デフォルトの接続に対して実行されます。逆に、非デフォルトの接続は、AT句を持つCONNECT文によって行われます。AT句を持つSQL文は、非デフォルトの接続に対して実行されます。
データベース名はすべて一意である必要があります。ただし、複数のデータベース名で同じ接続を指定できます。つまり、任意のノード上のデータベースに対して複数の接続を持つことができます。
通常は、Oracleへの接続を次のように確立します。
EXEC SQL CONNECT :username IDENTIFIED BY :password;
また、次の文も使用できます。
EXEC SQL CONNECT :usr_pwd;
usr_pwdには、username/passwordが含まれます。
次のユーザーIDを使用してOracleに自動的に接続できます。
CLUSTER$username
usernameは現行のオペレーティング・システムのユーザー名またはタスク名、CLUSTER$usernameは有効なOracleユーザーIDです。プリコンパイラに渡すのは、スラッシュ文字(/)のみです。次に例を示します。
char oracleid = '/'; ... EXEC SQL CONNECT :oracleid;
これにより、ユーザーCLUSTER$usernameで自動的に接続されます。
データベースとノードを指定しない場合、カレント・ノードのデフォルトのデータベースに接続されます。別のデータベースに接続する場合は、そのデータベースを明示的に識別する必要があります。
明示的接続では、SQL文で参照される接続名を指定して、別のデータベースに直接接続します。同時に複数のデータベースに接続することも、同じデータベースに複数回接続することもできます。
次の例では、リモート・ノードにある単一の非デフォルトのデータベースに接続します。
/* declare needed host variables */ char username[10] = "scott"; char password[10] = "tiger"; char db_string[20] = "NYNON"; /* give the database connection a unique name */ EXEC SQL DECLARE DB_NAME DATABASE; /* connect to the nondefault database */ EXEC SQL CONNECT :username IDENTIFIED BY :password AT DB_NAME USING :db_string;
この例で使用されている識別子は、次の目的で使用されています。
ホスト変数usernameおよびpasswordは、有効なユーザーを識別します。
ホスト変数db_stringには、リモート・ノードにある非デフォルトのデータベースに接続するためのOracle Net構文が含まれています。
未宣言の識別子DB_NAMEは、非デフォルト接続の名前を指定します。これは、Oracleで使用される識別子で、ホスト変数でもプログラム変数でもありません。
USING句では、DB_NAMEに対応付けるネットワーク、マシンおよびデータベースを指定します。その後、AT句(DB_NAME付き)を使用しているSQL文は、db_stringに指定したデータベースで実行されます。
または、次のように、AT句で文字ホスト変数を使用できます。
/* declare needed host variables */ char username[10] = "scott"; char password[10] = "tiger"; char db_name[10] = "oracle1"; char db_string[20] = "NYNON"; /* connect to the nondefault database using db_name */ EXEC SQL CONNECT :username IDENTIFIED BY :password AT :db_name USING :db_string; ...
db_nameがホスト変数の場合、DECLARE DATABASE文は不要です。DB_NAMEが未宣言の識別子の場合にのみ、CONNECT ... AT DB_NAME文を実行する前にDECLARE DB_NAME DATABASE文を実行する必要があります。
権限が付与されている場合は、非デフォルト接続でSQL DML文を実行できます。たとえば、次の一連の文を入力します。
EXEC SQL AT DB_NAME SELECT ... EXEC SQL AT DB_NAME INSERT ... EXEC SQL AT DB_NAME UPDATE ...
次の例では、db_nameはホスト変数です。
EXEC SQL AT :db_name DELETE ...
db_nameがホスト変数の場合は、SQL文で参照されるすべてのデータベース表をDECLARE TABLE文で定義する必要があります。定義しないと、プリコンパイラで警告が発行されます。
PL/SQLブロックは、AT句を使用して実行できます。次の例は構文を示しています。
EXEC SQL AT :db_name EXECUTE begin /* PL/SQL block here */ end; END-EXEC;
カーソル制御文OPEN、FETCHおよびCLOSEなどは例外で、AT句を使用しません。カーソルを明示的に識別されたデータベースに対応付ける場合は、次のようにDECLARE CURSOR文でAT句を使用します。
EXEC SQL AT :db_name DECLARE emp_cursor CURSOR FOR ... EXEC SQL OPEN emp_cursor ... EXEC SQL FETCH emp_cursor ... EXEC SQL CLOSE emp_cursor;
db_nameがホスト変数の場合は、DECLAREされたカーソルを参照するすべてのSQL文の適用範囲内で宣言する必要があります。たとえば、あるサブプログラム内でカーソルをOPENし、別のサブプログラムでそのカーソルからFETCHする場合は、db_nameをグローバルに宣言する必要があります。
カーソルからOPEN、CLOSEおよびFETCHを実行する場合、AT句を使用しません。SQL文が実行されるのは、DECLARE CURSOR文のAT句で指定されたデータベースか、カーソル宣言でAT句が使用されていない場合はデフォルトのデータベースです。
AT :host_variable句を使用すると、カーソルに対応付けられた接続を変更できます。ただし、カーソルのオープン中は対応付けを変更できません。次の例を考えます。
EXEC SQL AT :db_name DECLARE emp_cursor CURSOR FOR ... strcpy(db_name, "oracle1"); EXEC SQL OPEN emp_cursor; EXEC SQL FETCH emp_cursor INTO ... strcpy(db_name, "oracle2"); EXEC SQL OPEN emp_cursor; /* illegal, cursor still open */ EXEC SQL FETCH emp_cursor INTO ...
この例は、2番目のOPEN文を実行するときにemp_cursorがまだオープンされているため、無効となります。接続ごとに別個のカーソルが維持されることはありません。emp_cursorは1つのみで、他の接続用に再オープンする前にクローズする必要があります。最後の例をデバッグするには、次のようにカーソルを再オープンする前にクローズします。
... EXEC SQL CLOSE emp_cursor; -- close cursor first strcpy(db_name, "oracle2"); EXEC SQL OPEN emp_cursor; EXEC SQL FETCH emp_cursor INTO ...
動的SQL文は、文中にAT句が使用されないカーソル制御文に似ています。
動的SQL方法1では、非デフォルト接続で文を実行する場合はAT句を使用する必要があります。次に例を示します。
EXEC SQL AT :db_name EXECUTE IMMEDIATE :sql_stmt;
動的SQL方法2、3および4では、非デフォルト接続で文を実行する場合は、DECLARE STATEMENT文でのみAT句を使用します。PREPARE、DESCRIBE、OPEN、FETCHおよびCLOSEなど、他の動的SQL文では、いずれもAT句を使用しません。次の例は、方法2を示しています。
EXEC SQL AT :db_name DECLARE sql_stmt STATEMENT; EXEC SQL PREPARE sql_stmt FROM :sql_string; EXEC SQL EXECUTE sql_stmt;
次の例は、方法3を示しています。
EXEC SQL AT :db_name DECLARE sql_stmt STATEMENT; EXEC SQL PREPARE sql_stmt FROM :sql_string; EXEC SQL DECLARE emp_cursor CURSOR FOR sql_stmt; EXEC SQL OPEN emp_cursor ... EXEC SQL FETCH emp_cursor INTO ... EXEC SQL CLOSE emp_cursor;
単一の明示的接続の場合と同様に、複数の明示的接続にはAT db_name句を使用できます。次の例では、2つの非デフォルトのデータベースに同時に接続しています。
/* declare needed host variables */ char username[10] = "scott"; char password[10] = "tiger"; char db_string1[20] = "NYNON1"; char db_string2[20] = "CHINON"; ... /* give each database connection a unique name */ EXEC SQL DECLARE DB_NAME1 DATABASE; EXEC SQL DECLARE DB_NAME2 DATABASE; /* connect to the two nondefault databases */ EXEC SQL CONNECT :username IDENTIFIED BY :password AT DB_NAME1 USING :db_string1; EXEC SQL CONNECT :username IDENTIFIED BY :password AT DB_NAME2 USING :db_string2;
識別子DB_NAME1およびDB_NAME2を宣言し、2つの非デフォルト・ノードのデフォルトのデータベースの名前を指定します。これにより、SQL文ではデータベースを名前で参照できます。
または、AT句でホスト変数を使用できます。次に例を示します。
/* declare needed host variables */ char username[10] = "scott"; char password[10] = "tiger"; char db_name[20]; char db_string[20]; int n_defs = 3; /* number of connections to make */ ... for (i = 0; i < n_defs; i++) { /* get next database name and OracleNet string */ printf("Database name: "); gets(db_name); printf("OracleNet) string: "); gets(db_string); /* do the connect */ EXEC SQL CONNECT :username IDENTIFIED BY :password AT :db_name USING :db_string; }
この方法を使用して、同一のデータベースに対して複数の接続を行うこともできます。次に例を示します。
strcpy(db_string, "NYNON"); for (i = 0; i < ndefs; i++) { /* connect to the nondefault database */ printf("Database name: "); gets(db_name); EXEC SQL CONNECT :username IDENTIFIED BY :password AT :db_name USING :db_string; } ...
複数の接続に同じOracle Net文字列を使用する場合も、接続ごとに異なるデータベース名を使用する必要があります。ただし、データベース名ではデフォルトおよび非デフォルトのデータベースの両方が識別されるため、1つのデータベース名で同じデータベースに2回接続できます。
アプリケーション・プログラムでは、複数のリモート・データベースにあるデータを操作するトランザクションの整合性を確認する必要があります。つまり、プログラムはトランザクションのすべてのSQL文をコミットまたはロールバックする必要があります。これは、ネットワーク障害が発生した場合や、システムの1つがクラッシュした場合は不可能です。
たとえば、2つの会計データベースで作業しているとします。一方のデータベースで勘定の借方に記帳し、他方のデータベースで勘定の貸方に記帳してから、それぞれのデータベースでCOMMITを発行します。両方のトランザクションがコミットまたはロールバックされたかどうかは、プログラム側で確認する必要があります。
暗黙的接続は、Oracleの分散問合せ機能を通じてサポートされます。この機能には明示的接続は不要ですが、サポートされるのはSELECT文のみです。分散問合せを使用すると、単一のSELECT文で1つ以上の非デフォルトのデータベースにあるデータにアクセスできます。
分散問合せ機能にはデータベース・リンクが利用されます。この場合は、接続自体ではなくCONNECT文に名前を割り当てます。 実行時には、指定したOracleサーバーにより埋込みSELECT文が実行され、非デフォルトのデータベースに暗黙的に接続され、必要なデータが取得されます。
次の例では、単一の非デフォルトのデータベースに接続しています。最初に、プログラムでは次の文が実行され、データベース・リンクが定義されます(通常、データベース・リンクは、DBAまたはユーザーが対話形式で確立します)。
EXEC SQL CREATE DATABASE LINK db_link CONNECT TO username IDENTIFIED BY password USING 'NYNON';
プログラムでは、次のようにデータベース・リンクを使用して非デフォルトのEMP表を問い合せできます。
EXEC SQL SELECT ENAME, JOB INTO :emp_name, :job_title FROM emp@db_link WHERE DEPTNO = :dept_number;
データベース・リンクは、埋込みSQL文のAT句に使用されるデータベース名とは無関係です。単に、Oracleに対して、非デフォルトのデータベースの位置、データベースへのパス、使用するOracleユーザー名とパスワードを指示します。データベース・リンクは、明示的に削除されるまではデータ・ディクショナリに格納されます。
前述の例で、デフォルトのOracleサーバーは、データベース・リンクdb_linkを使用して、Oracle Net経由で非デフォルトのデータベースにログインします。問合せはデフォルトのサーバーに送られますが、非デフォルトのデータベースに転送されて実行されます。
データベース・リンクを簡単に参照できるように、次のようにシノニムを対話形式で作成できます。
EXEC SQL CREATE SYNONYM emp FOR emp@db_link;
プログラムでは、次のように非デフォルトのEMP表を問い合せできます。
EXEC SQL SELECT ENAME, JOB INTO :emp_name, :job_title FROM emp WHERE DEPTNO = :dept_number;
次の例では、2つの非デフォルトのデータベースに同時に接続しています。まず次の一連の文を実行し、2つのデータベース・リンクを定義して2つのシノニムを作成します。
EXEC SQL CREATE DATABASE LINK db_link1 CONNECT TO username1 IDENTIFIED BY password1 USING 'NYNON'; EXEC SQL CREATE DATABASE LINK db_link2 CONNECT TO username2 IDENTIFIED BY password2 USING 'CHINON'; EXEC SQL CREATE SYNONYM emp FOR emp@db_link1; EXEC SQL CREATE SYNONYM dept FOR dept@db_link2;
プログラムでは、次のように非デフォルトのEMP表とDEPT表を問い合せできます。
EXEC SQL SELECT ENAME, JOB, SAL, LOC FROM emp, dept WHERE emp.DEPTNO = dept.DEPTNO AND DEPTNO = :dept_number;
Oracleにより、db_link1にある非デフォルトのEMP表とdb_link2にある非デフォルトのDEPT表が結合され、問合せが実行されます。
トランザクションの本題に入る前に、この項で定義されている用語を理解する必要があります。
Oracleが管理するジョブまたはタスクは、セッションと呼ばれます。アプリケーション・プログラムまたはSQL*Formsなどのツールを実行してデータベースに接続すると、ユーザー・セッションが開始されます。
Oracleでは、複数のユーザー・セッションを同時に機能させ、コンピュータ・リソースを共有させることができます。そのために、Oracleは同時実行性、つまり多数のユーザーによる同一データへのアクセスを制御する必要があります。同時実行性制御が十分ではないと、データの整合性が失われる可能性があります。つまり、データや構造への変更が誤った順序で実行される可能性があります。
Oracleでは、ロック(エンキューとも呼ばれます)を使用してデータへの同時アクセスを制御します。ロックを使用すると、データの表または行のようなデータベース・リソースを一時的に専有できます。そのため、ロックを使用しているユーザーが変更を終了するまで、他のユーザーはデータを変更できません。
デフォルトのロック機能がOracleのデータと構造体を保護するため、明示的にロックする必要はありません。ただし、デフォルトのロックを上書きした方がユーザーにとって有益な場合は、表または行に対するデータ・ロックを要求できます。行の共有や排他など、数種類のロック・モードから選択できます。
複数のユーザーが同じデータベース・オブジェクトへのアクセスを試みると、デッドロックが発生することがあります。たとえば、2人のユーザーが同じ表を更新している場合、互いに相手側が現在ロックしている行の更新を試みると、双方が待機状態になります。相手が使用中のリソースを各ユーザーが待機しているため、Oracleによりデッドロックが解除されるまでは、どちらも処理を継続できません。Oracleでは、最低作業量を完了した関連するトランザクションにエラー信号が送られ、「リソース待機の間にデッドロックが検出されました。」というOracleエラー・コードがSQLCAのsqlcodeに戻されます。
1人のユーザーが問合せ中である表を、同時に別のユーザーが更新している場合、問合せ用表データの読取り一貫性ビューが生成されます。つまり、ある問合せが開始され、進行していく間、その問合せによって読み込まれたデータは変更されません。更新アクティビティが継続している間、Oracleは表のデータのスナップショットをとり、変更内容をロールバック・セグメントの中に記録します。Oracleでは、ロールバック・セグメント内の情報に基づいて、読取り一貫性のある問合せ結果が作成され、必要に応じて変更内容が取り消されます。
Oracleはトランザクション指向です。つまり、トランザクションを使用してデータの整合性が確保されます。トランザクションとは、あるタスクを完了するために定義する1つ以上の論理的に関連付けられたSQL文です。Oracleでは、一連のSQL文を一単位として扱い、それらの文によって実行されたすべての変更が、同時にコミット(確定)されるか、ロールバック(取消し)されます。トランザクションの途中でアプリケーション・プログラムに障害が発生すると、データベースは自動的にトランザクション前の状態にリストアされます。
以降の項では、トランザクションの定義および制御方法について説明します。特に、次の操作方法を学習します。
データベースへの接続
同時接続
トランザクションの開始および終了
COMMIT文を使用したトランザクションの確定
ROLLBACK TO文とともにSAVEPOINT文を使用したトランザクションの部分的な取消し
ROLLBACK文を使用したトランザクション全体の取消し
RELEASEオプションの指定によるリソースの解放とデータベースのログオフ
SET TRANSACTION文を使用した読取り専用トランザクションの設定
FOR UPDATE句またはLOCK TABLE文を使用したデフォルト・ロックの上書き
この章で説明するSQL文の詳細は、『Oracle Database SQL言語リファレンス』を参照してください。
プログラムの最初の実行SQL文(CONNECT以外)によりトランザクションを開始します。1つのトランザクションが終了すると、次の実行SQL文で自動的に別のトランザクションが開始されます。つまり、すべての実行文はトランザクションの一部です。宣言SQL文はロールバックされません。また、コミットする必要もないため、トランザクションの一部とみなされません。
COMMITまたはROLLBACK文を記述します。RELEASEオプションは、指定してもしなくてもかまいません。これにより、データベースへの変更を明示的に確定または取り消します。
実行の前後で自動COMMITを発行するデータ定義文(ALTER、CREATEまたはGRANTなど)を記述します。これにより、データベースへの変更を暗黙的に確定します。
システム障害が発生した場合や、ソフトウェアの問題、ハードウェアの問題または強制割込みなどが原因で、予期しないユーザー・セッション停止が発生した場合にも、トランザクションは終了します。そのトランザクションはOracleによりロールバックされます。
トランザクション中にプログラムに障害が発生すると、Oracleがエラーを検出して、トランザクションをロールバックします。オペレーティング・システムに障害が発生すると、データベースがトランザクション前の状態にリストアされます。
プログラムをCOMMIT文またはROLLBACK文で分割しなければ、Oracleではそのプログラム全体が1つのトランザクションとみなされます(ただし、そのプログラムにデータ定義文が含まれている場合は、自動COMMITが発行されます)。
COMMIT文を使用すると、データベースへの変更を確定できます。変更をCOMMITするまで、他のユーザーは変更されたデータにアクセスできず、トランザクションの開始前の状態のデータが表示されます。特に、COMMIT文では次の処理が実行されます。
現行のトランザクション中にデータベースに対して行った変更をすべて確定します。
これらの変更を他のユーザーが参照できるようにします。
すべてのセーブポイントを消去します(次の項を参照)。
解析ロックを除き、すべての行と表のロックを解除します。
CURRENT OF句で参照されているカーソルをクローズします。MODE=ANSIの場合は、COMMIT文に指定されている接続の明示カーソルをすべてクローズします。
トランザクションを終了します。
COMMIT文は、ホスト変数の値にも、プログラムの制御フローにも影響しません。
MODE=ORACLEの場合、CURRENT OF句で参照されていない明示カーソルは、COMMITの前後もオープンしたままです。これによりパフォーマンスが向上します。
これらの処理は通常の処理の一部になっているため、COMMIT文はプログラムのメイン・パスにインラインで設定する必要があります。プログラムの終了前に、保留中の変更を明示的にCOMMITする必要があります。明示的にCOMMITしない場合、この種の変更はロールバックされます。次の例では、トランザクションをコミットしてOracleから切断します。
EXEC SQL COMMIT WORK RELEASE;
オプションのキーワードWORKは、ANSI互換性を提供します。RELEASEオプションを指定すると、プログラムで使用されているOracleリソース(ロックとカーソル)がすべて解放され、データベースからログオフされます。
データ定義文では、実行の前後に自動COMMITが発行されるため、データ定義文の後にCOMMIT文を記述する必要はありません。したがって、データ定義文が成功しても失敗しても、直前のトランザクションがコミットされます。
SAVEPOINT文を使用すると、トランザクション処理中にカレント・ポイントにマークを付けて名前を指定できます。マーク付きのポイントは、セーブポイントと呼ばれます。たとえば、次の文では、start_deleteというセーブポイントを設定しています。
EXEC SQL SAVEPOINT start_delete;
セーブポイントを使用すると長いトランザクションを分割できるため、より複雑なプロシージャを厳密に制御できます。たとえば、トランザクションで複数の関数が実行される場合は、各関数の前にセーブポイントを設定できます。これにより、ある関数が失敗した場合も、Oracleデータを前の状態に簡単にリストアし、リカバリしてから、その関数を再実行できます。
トランザクションの一部を取り消すには、ROLLBACK文とそのTO SAVEPOINT句を使用してセーブポイントを指定します。次の例では、表MAIL_LISTにアクセスして新しいリストを挿入し、古いリストを更新して、アクティブでない(少数の)リストを削除しています。削除後に、SQLCA内のsqlerrdの3番目の要素で、削除された行数を調べます。削除された行数が予想以上に多い場合は、セーブポイントstart_deleteまでロールバックして、その削除のみを取り消します。
... for (;;) { printf("Customer number? "); gets(temp); cust_number = atoi(temp); printf("Customer name? "); gets(cust_name); EXEC SQL INSERT INTO mail_list (custno, cname, stat) VALUES (:cust_number, :cust_name, 'ACTIVE'); ... } for (;;) { printf("Customer number? "); gets(temp); cust_number = atoi(temp); printf("New status? "); gets(new_status); EXEC SQL UPDATE mail_list SET stat = :new_status WHERE custno = :cust_number; } /* mark savepoint */ EXEC SQL SAVEPOINT start_delete; EXEC SQL DELETE FROM mail_list WHERE stat = 'INACTIVE'; if (sqlca.sqlerrd[2] < 25) /* check number of rows deleted */ printf("Number of rows deleted is %d\n", sqlca.sqlerrd[2]); else { printf("Undoing deletion of %d rows\n", sqlca.sqlerrd[2]); EXEC SQL WHENEVER SQLERROR GOTO sql_error; EXEC SQL ROLLBACK TO SAVEPOINT start_delete; } EXEC SQL WHENEVER SQLERROR CONTINUE; EXEC SQL COMMIT WORK RELEASE; exit(0); sql_error: EXEC SQL WHENEVER SQLERROR CONTINUE; EXEC SQL ROLLBACK WORK RELEASE; printf("Processing error\n"); exit(1);
あるセーブポイントまでロールバックすると、そのセーブポイント以降にマークされたセーブポイントはすべて消去されます。ただし、ロールバック先のセーブポイントは消去されません。たとえば、セーブポイントを5つマークし、3番目のセーブポイントまでロールバックすると、4番目と5番目のセーブポイントのみが消去されます。
2つのセーブポイントに同じ名前を付けると、最初のセーブポイントが消去されます。COMMIT文またはROLLBACK文では、すべてのセーブポイントが消去されます。
ROLLBACK文を使用すると、データベースに対して保留中の変更を取り消すことができます。たとえば、表から間違った行を削除したなどの場合に、ROLLBACKを使用すると元のデータをリストアできます。TO SAVEPOINT句を使用すると、現行のトランザクションの途中の文までロールバックできます。したがって、変更をすべて取り消す必要はありません。
未完成のトランザクションを開始した場合(たとえば、SQL文が正常に実行されないなど)、ROLLBACKを使用すると起点まで戻ることができるため、データベースの整合性が維持されます。特に、ROLLBACK文では次の処理が実行されます。
現行のトランザクション中にデータベースに対して行った変更をすべて取り消します。
すべてのセーブポイントを消去します。
トランザクションを終了します。
解析ロックを除き、すべての行と表のロックを解除します。
CURRENT OF句で参照されているカーソルをクローズします。MODE=ANSIの場合は、すべての明示カーソルをクローズします。
ROLLBACK文は、ホスト変数の値にも、プログラムの制御フローにも影響しません。
MODE=ORACLEの場合、CURRENT OF句で参照されていない明示カーソルは、ROLLBACKの前後もオープンしたままです。
特に、ROLLBACK TO SAVEPOINT文では次の処理が実行されます。
指定されたセーブポイント以後のデータベースへの変更を取り消します。
指定されたセーブポイント以後のすべてのセーブポイントを消去します。
指定されたセーブポイント以後に取得されたすべての行ロックと表ロックを解除します。
ROLLBACK文は例外処理の一部になっているため、プログラムのメイン・パスではなくエラー処理ルーチン内に指定する必要があります。次の例では、トランザクションをロールバックしてOracleから切断します。
EXEC SQL ROLLBACK WORK RELEASE;
オプションのキーワードWORKは、ANSI互換性を提供します。RELEASEオプションを指定すると、プログラムで使用されているリソースがすべて解放され、データベースから切断されます。
WHENEVER SQLERROR GOTO文からエラー処理ルーチンに分岐するときに、そのルーチンにROLLBACK文が含まれている場合は、ROLLBACK文でエラーが発生すると、プログラムが無限ループに入るおそれがあります。このような無限ループは、次のようにROLLBACK文の前にWHENEVER SQLERROR CONTINUEを記述することで回避できます。
EXEC SQL WHENEVER SQLERROR GOTO sql_error; for (;;) { printf("Employee number? "); gets(temp); emp_number = atoi(temp); printf("Employee name? "); gets(emp_name); EXEC SQL INSERT INTO emp (empno, ename) VALUES (:emp_number, :emp_name); ... } ... sql_error: EXEC SQL WHENEVER SQLERROR CONTINUE; EXEC SQL ROLLBACK WORK RELEASE; printf("Processing error\n"); exit(1);
プログラムが異常終了すると、Oracleによりトランザクションが自動的にロールバックされます。
SQL文を実行する前に、Oracleでは暗黙的なセーブポイント(ユーザーは使用不可)が設定されます。したがって、この文が失敗すると、自動的にロールバックされ、SQLCA内のsqlcodeに適切なエラー・コードが戻されます。たとえば、INSERT文で一意の索引に重複する値を挿入したためにエラーが発生すると、その文はロールバックされます。
Oracleでは、デッドロックを解消するためにSQL文を1文のみロールバックすることもできます。Oracleは関係しているトランザクションの1つにエラーを通知し、そのトランザクション中の現在の文をロールバックします。
失われるのは失敗したSQL文で開始された作業のみです。つまり、現行のトランザクション内でこの文より前に行われた作業は保存されます。したがって、データ定義文が失敗しても、それ以前の自動コミットは取り消されません。
SQL文を実行する前に、Oracleはその文を解析する必要があります。つまり、その文が構文規則に従っているか、あるいは有効なデータベース・オブジェクトを参照しているかを確認する必要があります。SQL文の実行時に検出されたエラーはロールバックを引き起こしますが、文の解析の際に検出されたエラーはロールバックを引き起こしません。
プログラムが異常終了すると、Oracleにより変更が自動的にロールバックされます。異常終了が発生するのは、プログラムが作業を明示的にコミットもロールバックもせずに、RELEASEオプションを使用してOracleから切断する場合です。正常終了が発生するのは、プログラムが正常に実行され、オープン状態のカーソルがクローズされ、作業が明示的にコミットまたはロールバックされ、Oracleから切断され、制御がユーザーに戻された場合です。
最後に実行されるSQL文が次のどちらかの場合、プログラムは正常終了します。
EXEC SQL COMMIT WORK RELEASE;
または
EXEC SQL ROLLBACK WORK RELEASE;
トークンWORKはオプションです。前述の場合以外は、ユーザー・セッションが取得したロックおよびカーソルは、そのユーザー・セッションがアクティブでなくなったことがOracleで認識されるまでは、プログラムの終了後も保持されます。このため、マルチユーザー環境では、ロックされたリソースを他のユーザーが待機する時間が長くなることがあります。
SET TRANSACTION文を使用すると、読取り専用トランザクションを開始できます。読取り専用トランザクションでは反復可能読取りが可能になるため、他のユーザーが更新中の1つ以上の表に対して複数の問合せを実行する場合に便利です。SET TRANSACTION文の例を次に示します。
EXEC SQL SET TRANSACTION READ ONLY;
SET TRANSACTION文は、読取り専用トランザクションの最初のSQL文である必要があり、1つのトランザクションで1回しか使用できません。READ ONLYパラメータは必須です。READ ONLYパラメータを使用しても、他のトランザクションには影響しません。
読取り専用トランザクションで使用できるのは、SELECT文、COMMIT文およびROLLBACK文のみです。たとえば、INSERT文、DELETE文またはSELECT FOR UPDATE OF文を使用するとエラーが発生します。
読取り専用トランザクションでは、複数の表と複数の問合せで構成された読取り一貫性ビューが作成され、すべての問合せがデータベースの同一のスナップショットを参照します。他のユーザーは、通常の方法でデータの問合せや更新ができます。
読取り専用トランザクションは、COMMIT文、ROLLBACK文またはデータ定義文で終了します。(データ定義文では暗黙的COMMITが発行されることを思い出してください。)
次の例では、店の管理者が読取り専用トランザクションを使用して、当日、先週および先月の売上を調べ、要約レポートを生成しています。トランザクションの途中で他のユーザーがデータベースを更新しても、レポートには影響しません。
EXEC SQL SET TRANSACTION READ ONLY; EXEC SQL SELECT sum(saleamt) INTO :daily FROM sales WHERE saledate = SYSDATE; EXEC SQL SELECT sum(saleamt) INTO :weekly FROM sales WHERE saledate > SYSDATE - 7; EXEC SQL SELECT sum(saleamt) INTO :monthly FROM sales WHERE saledate > SYSDATE - 30; EXEC SQL COMMIT WORK; /* simply ends the transaction since there are no changes to make permanent */ /* format and print report */
デフォルトでは、多数のデータ構造がOracleにより自動的にロックされます。ただし、デフォルトのロックを無効にして、別のロックを有効にするときは、行や表を特定して、そこにデータ・ロックを要求できます。明示的なロックにより、トランザクション中に表に対するアクセスを共有または制限したり、複数の表および複数の問合せの読取り一貫性を保持したりできます。
SELECT FOR UPDATE OF文を使用すると、表の特定行を明示的にロックすることで、UPDATEまたはDELETEが実行されるまで、その行が変更されないようにできます。ただし、OracleではUPDATE時またはDELETE時に行レベルのロックが自動的に取得されます。したがって、UPDATEまたはDELETEの前に行をロックする場合にのみ、FOR UPDATE OF句を使用してください。
LOCK TABLE文を使用すると、表全体を明示的にロックできます。
UPDATE文またはDELETE文のCURRENT OF句で参照されるカーソルをDECLAREする場合は、FOR UPDATE OF句を使用すると行の排他ロックを取得できます。SELECT FOR UPDATE OF文では、更新または削除の対象となる行が識別され、アクティブ・セット内の各行がロックされます。これは、ある行内の既存の値に基づいて更新処理を行う場合に便利です。更新前に、その行が他のユーザーにより変更されないようにする必要があります。
FOR UPDATE OF句はオプションです。たとえば、次のようなコードがあるとします。
EXEC SQL DECLARE emp_cursor CURSOR FOR SELECT ename, job, sal FROM emp WHERE deptno = 20 FOR UPDATE OF sal;
ここでFOR UPDATE OF句を削除すると、次のようにコードが単純になります。
EXEC SQL DECLARE emp_cursor CURSOR FOR SELECT ename, job, sal FROM emp WHERE deptno = 20;
CURRENT OF句では、必要に応じてFOR UPDATE句を追加するようにプリコンパイラに指示します。CURRENT OF句を使用して、カーソルから最後にFETCHされた行を参照します。
LOCK TABLE文を使用すると、指定したロック・モードで1つ以上の表をロックできます。たとえば、次の文は行共有モードでEMP表をロックします。行共有ロックにより表への同時アクセスが可能になります。つまり、他のユーザーがその表全体をロックして排他的に使用することはできません。
EXEC SQL LOCK TABLE EMP IN ROW SHARE MODE NOWAIT;
ロック・モードによって、その表に設定できる他のロックが決定されます。たとえば、同時に多数のユーザーが1つの表に対して行共有ロックを取得できる一方で、排他ロックを取得できるのは一度に1ユーザーのみです。あるユーザーが表を排他ロックしている間は、他のユーザーはその表の行をINSERT、UPDATEまたはDELETEできません。
ロック・モードの詳細は、『Oracle Database概要』を参照してください。
オプションのキーワードNOWAITを指定すると、他のユーザーが表をロックしている場合は、その表の解放を待機しないようにOracleに対して指示できます。制御はただちにプログラムに戻されるため、プログラムではロックの取得を再度試みるまでの間に別の作業ができます。(SQLCA内のsqlcodeをチェックすると、LOCK TABLEが失敗したか確認できます。)NOWAITを省略すると、Oracleはその表が使用可能になるまで待機状態になります。この待機時間に制限はありません。
表がロックされていても、他のユーザーは表に対して問い合せできますが、問合せを実行しても表のロックを取得できません。したがって、問合せが他の問合せや更新を妨げることはなく、更新が問合せを妨げることもありません。2つの異なるトランザクションで同じ行が更新される場合にのみ、一方のトランザクションは他方のトランザクションが終了するまで待機状態になります。
COMMITとFETCHを併用する場合は、CURRENT OF句を使用しないでください。かわりに、各行のROWIDをSELECTしてから、その値を使用して更新または削除中の現在行を識別します。次に例を示します。
... EXEC SQL DECLARE emp_cursor CURSOR FOR SELECT ename, sal, ROWID FROM emp WHERE job = 'CLERK'; ... EXEC SQL OPEN emp_cursor; EXEC SQL WHENEVER NOT FOUND GOTO ... for (;;) { EXEC SQL FETCH emp_cursor INTO :emp_name, :salary, :row_id; ... EXEC SQL UPDATE emp SET sal = :new_salary WHERE ROWID = :row_id; EXEC SQL COMMIT; ... }
ただし、FETCHされた行はロックされません。つまり、ある行を読み込んでも、その行を更新または削除する前に他のユーザーがその行を変更すると、一貫性のない結果が生じる場合があります。
分散データベースとは、異なるノード上の複数の物理データベースで構成される単一の論理データベースです。分散型の文とは、データベース・リンクを使用してリモート・ノードにアクセスする任意のSQL文です。分散トランザクションには、分散データベースの複数のノードでデータを更新する分散型の文が少なくとも1つは設定されています。その更新が1つのノードにのみ影響する場合、トランザクションは分散型ではありません。
COMMITを発行すると、分散トランザクションによる影響を受けた各データベースの変更が確定されます。COMMITのかわりにROLLBACKを発行すると、すべての変更が取り消されます。ただし、コミットまたはロールバック中にネットワークやマシンで障害が発生すると、分散トランザクションの状態は不明またはインダウトになることがあります。その場合に、FORCE TRANSACTIONシステム権限があれば、FORCE句を使用してローカル・データベースでトランザクションを手動でコミットまたはロールバックできます。このトランザクションは、トランザクションIDを引用符付きリテラルで囲んで指定する必要があります。トランザクションIDは、データ・ディクショナリ・ビューDBA_2PC_PENDINGにあります。次に例を示します。
EXEC SQL COMMIT FORCE '22.31.83'; ... EXEC SQL ROLLBACK FORCE '25.33.86';
FORCEでは、指定したトランザクションのみがコミットまたはロールバックされるため、現行のトランザクションには影響しません。インダウト・トランザクションは、手動でセーブポイントまでロールバックできません。
COMMIT文中のCOMMENT句を使用すると、分散トランザクションに対応付けるコメントを指定できます。トランザクションがインダウトになると、COMMENTで指定したテキストが、OracleによりトランザクションIDとともにデータ・ディクショナリ・ビューDBA_2PC_PENDINGに格納されます。長さ50文字以内の引用符付きリテラルを指定する必要があります。次に例を示します。
EXEC SQL COMMIT COMMENT 'In-doubt trans; notify Order Entry';
注意: COMMENT句は、将来のリリースでは廃止になる予定です。COMMENT句ではなくトランザクション名を使用することをお薦めします。 |
分散トランザクションの詳細は、『Oracle Database概要』を参照してください。
一般的な問題を回避するには、次のガイドラインに従ってください。
アプリケーションの設計時には、論理的に関連する処理を1つのトランザクションにグループ化してください。適切に設計されたトランザクションには、特定のタスクを完了するために必要なすべてのステップが、過不足なく含まれています。
表内で参照するデータは一貫している必要があります。したがって、トランザクション内のSQL文は、一貫した方法でデータを変更する必要があります。たとえば、2つの銀行口座間の資金振替には、一方の口座の借方勘定と他方の口座の貸方勘定が含まれています。更新の成功または失敗は、双方で一致する必要があります。一方の口座への新規預金など、無関係な更新はトランザクションに含めないでください。