パフォーマンスを高めるために、Pro*C/C++アプリケーション開発者は、埋め込んだSQL文のホスト配列を参照できます。これにより、データベースへの1回のラウンドトリップでSQL文の配列を実行できます。配列の実行によってパフォーマンスが大幅に向上するにもかかわらず、ANSI規格ではないため、この機能を使用しない開発者もいます。たとえば、Oracle製品で配列の実行を使用するように記述されたアプリケーションは、IBMのプリコンパイラを使用してプリコンパイルすることはできません。
対処方法として、バッファ済INSERT文を使用すると、ANSI規格の埋込みSQL構文を保持しながらパフォーマンスを向上させることができます。
コマンドライン・オプション「max_row_insert」は、INSERT文の実行前にバッファする行の数を制御します。このオプションのデフォルトは0で、機能は無効化されています。この機能を有効化するには、0よりも大きい任意の数を指定します。
挿入バッファを有効化すると、プリコンパイラのランタイムが対応するカーソルにフラグを付け、次を実行します。
バインド変数を保持するための追加のメモリーを割当てまたは再割当てします(初回実行時のみ)。
プログラムのホスト変数から内部ランタイム・バインド構造にバインド変数をコピーします。
バッファ済の行カウントを増加させます。
MAX_INSERT_ROWSがバッファされている場合、バッファされたINSERT文をフラッシュします。
MAX_INSERT_ROWSに達していない場合は、フラッシュせずに、内部バインド・バッファに値をコピーした後、戻ります。
新しい埋込みSQL文が実行され、バッファ挿入文がフラッシュされる場合、次のことが実行されます。
バッファをフラッシュします。
フラッシュを求めたコールを続行します。
アプリケーションには、標準のプリコンパイラ・エラー・メカニズム(Pro*Cのsqlcaなど)を介して、エラーが知らされます。
「implicit_svpt」オプションは、新しくバッチ処理された挿入を開始する前に、暗黙的なセーブポイントを設定するかどうかを制御します。
YESの場合は、新しい行のバッチを開始する前に、セーブポイントが設定されます。挿入時にエラーが発生すると、暗黙的な「セーブポイントへのロールバック」が実行されます。
NOの場合は、暗黙的なセーブポイントが設定されません。バッファ済INSERTでエラーが発生すると、アプリケーションに通知されますが、ロールバックは実行されません。バッファ済INSERTのエラーは非同期に報告されます。アプリケーションでINSERT文が実行されると、挿入された行のエラーは報告されません。
挿入された行の一部のエラーは、INSERT以外の文が初めて実行されたときに後で報告されます。これには、DELETE、UPDATE、(別の表に対する)INSERT、COMMITおよびROLLBACKが含まれます。バッファ済INSERT文をクローズする文は、すべてエラーを報告します。この場合、エラーを報告する文は実行されません。エラーを処理した後で、バッファ済INSERTのエラーを報告した文を再実行する必要があります。それ以外の場合は、トランザクションをロールバックして再実行します。
たとえば、COMMIT文を使用して、バッファ済INSERTのループをクローズする方法について考えてみます。COMMITでは、以前のINSERTの重複キーが原因でエラーが発生します。この場合、COMMITは実行されません。エラーを処理した後で、COMMITを再実行する必要があります。それ以外の場合は、トランザクションをロールバックして再実行します。
挿入自体についてもいくつかエラーが報告され、以前に挿入された行のエラーが反映されている可能性があります。そのような場合、それ以上挿入は実行されません。以前挿入された行のエラーを処理し、現行の挿入を継続する必要がありますが、それには時間がかかります。かわりに、トランザクションをロールバックし、再実行してもかまいません。
たとえば、内部バッファの制限が10行で、アプリケーションがループで15行を挿入しているとします。8行目でエラーが発生したとします。エラーは、11行目が挿入されたときに報告され、これ以後にINSERTは実行されません。
バッファ済INSERT中に発生する可能性のあるエラーの一部を次に示します。
ORA-00001: 索引キーが重複しています
ORA-01400: 挿入時に必須列(NOT NULL)がないか、NULLになっています
ORA-01401: 列に挿入した値が大きすぎます。
ORA-01438: 指定した精度を超えた値が列に指定されています
例8-2 表へのバッファ行の挿入
このプログラムでは、EMP表に行のLOOPCNT数を挿入します。loop counter=5の場合、このプログラムは無効なempnoの挿入を試行します。max_row_insertオプションを使用しないと、プログラムでは無効な行を除くすべての行が挿入されます。max_row_insertオプションをLOOPCNTに設定すると、最初の4行のみが挿入されます。
max_row_insertオプションを使用すると、間違った文が削除されるときに、プログラムは配列INSERTプログラムと同様に動作します。
/*
* bufinsdemo.pc
*/
#include <stdio.h>
#include <string.h>
#include <stdlib.h>
#include <sqlda.h>
#include <sqlcpr.h>
#include <sqlca.h>
/* Number of rows to be inserted into the table */
#define LOOPCNT 100
/* Define a host structure
for inserting data into the table
and for fetching data from the table */
struct emprec
{
int empno;
varchar ename[10];
varchar job[9];
int mgr;
char hiredate[10];
float sal;
float comm;
int deptno;
};
typedef struct emprec buffinstyp;
/* Function prototypes */
void sql_error();
void insertdata();
void fetchdata();
void printempdata(buffinstyp);
void main()
{
exec sql begin declare section;
char *uid = "scott/tiger";
exec sql end declare section;
exec sql whenever sqlerror do sql_error();
exec sql connect :uid;
printf("\nInserting %d rows into EMP table.\n", LOOPCNT);
insertdata();
printf("\nFetching inserted data from EMP table.\n");
fetchdata();
exec sql delete from emp where empno < 1000;
exec sql commit work release;
exit(EXIT_SUCCESS);
}
/* Inserting data into the table */
void insertdata()
{
int i, cnt;
char *str;
buffinstyp emp_in;
/* To store temporary strings */
str = (char *)malloc (25 * sizeof(char));
/*
* When max_row_insert option is set to LOOPCNT and when the errorneous
* statement is removed, all the rows will be inserted into the database in
* one stretch and hence maximum performance gain will be achieved.
*/
for (i = 1; i <= LOOPCNT; i++)
{
if (i != 5)
emp_in.empno = i;
else
/* Errorneous statement. In emp table, empno is defined as number(4). */
emp_in.empno = 10000;
sprintf(str, "EMP_%03d", i);
strcpy (emp_in.ename.arr, str);
emp_in.ename.len = strlen (emp_in.ename.arr);
sprintf(str, "JOB_%03d", i);
strcpy (emp_in.job.arr, str);
emp_in.job.len = strlen (emp_in.job.arr);
emp_in.mgr = i+1001;
sprintf(str, "%02d-MAY-06", (i%30));
strcpy (emp_in.hiredate, str);
emp_in.sal = (i) * 10;
emp_in.comm = (i) * 0.1;
emp_in.deptno = 10;
exec sql insert into emp values (:emp_in);
}
free (str);
exec sql commit;
exec sql select count(*) into :cnt from emp where ename like 'EMP_%';
printf ("Number of rows successfully inserted into emp table: %d\n", cnt);
}
/* Fetches data from the table*/
void fetchdata()
{
buffinstyp emp_out;
/* Declares cursor to fetch only the rows that are inserted */
exec sql declare c1 cursor for
select empno, ename, job, mgr, hiredate, sal, comm, deptno
from emp where ename like 'EMP_%' order by empno;
exec sql open c1;
exec sql whenever not found do break;
while(1)
{
/* Fetches single row at each call */
exec sql fetch c1 into :emp_out;
printempdata(emp_out);
}
exec sql whenever not found do sql_error();
exec sql close c1;
}
/* Prints the fetched employee data */
void printempdata(buffinstyp emp_out)
{
emp_out.ename.arr[emp_out.ename.len] = '\0';
emp_out.job.arr[emp_out.job.len] = '\0';
printf("Empno=%d, Ename=%s, Job=%s, Mgr=%d, Hiredate=%s, Sal=%6.2f,\n"
"Comm=%5.2f, Deptno=%d\n", emp_out.empno, emp_out.ename.arr,
emp_out.job.arr, emp_out.mgr, emp_out.hiredate, emp_out.sal,
emp_out.comm, emp_out.deptno);
}
/* Error handling function. */
void sql_error()
{
printf("Error %s\n", sqlca.sqlerrm.sqlerrmc);
printf(" Rows Processed: %d\n", sqlca.sqlerrd[2]);
printf(" Rows Rolled Back: %d\n", sqlca.sqlerrd[0]);
}