区画環境におけるパフォーマンス向上の利点を利用するために、特別なプログラミング技法の使用を考慮してください。たとえば、アプリケーションが複数のデータベース・マネージャー区画から DB2 データにアクセスする場合、ここに説明する情報を考慮する必要があります。区画環境についての概説は、 管理の手引き および SQL 解説書 を参照してください。
読み取り専用のカーソルを宣言する場合は、 FOR READ ONLY または FOR FETCH ONLY を、OPEN CURSOR 宣言の中に含めてください。 (FOR READ ONLY と FOR FETCH ONLY は同等のステートメントです。) FOR READ ONLY カーソルによって、コーディネーター区画が一度に複数の行を取り出すことができるようになり、その後の FETCH ステートメントのパフォーマンスが大幅に向上します。 FOR READ ONLY カーソルを明示的に宣言しない場合は、コーディネーター区画がそれらを更新可能カーソルとして扱います。更新可能カーソルは、コーディネーター区画に 1 回の FETCH につき 1 行だけの取り出しを要求するので、パフォーマンスはかなり低下します。
オンライン・トランザクション処理 (OLTP) アプリケーションを最適化するために、すべてのデータ区画の処理を要求する単純 SQL ステートメントの使用を避けたい場合があるかもしれません。その場合、アプリケーションを設計して、 SQL ステートメントがただ 1 つの区画からデータを取り出せるようにします。この技法は、コーディネーター区画が、関連する 1 つまたはすべての区画と通信する際のパフォーマンスの低下を防ぎます。
分散サブセクション (DSS) は、並列照会に応じた処理を一部必要とするデータベース区画にサブセクションを送信するアクションです。また、DSS は呼び出し固有の値 (OLTP 環境にある変数の値など) を使って、サブセクションの始まりも記述します。指示 DSS は、表区分化キーを使用して、単一の区画を照会するように指示します。このタイプの照会をアプリケーションで使用して、すべてのノードに照会をブロードキャストした場合に発生するコーディネーター区画のオーバーヘッドを防ぎます。
指示 DSS の利点を生かした SELECT ステートメントの部分例は、以下のようになります。
SELECT ... FROM t1 WHERE PARTKEY=:hostvar
コーディネーター区画は照会を受け取ると、 :hostvar に対するデータのサブセットを保持している区画を判別して、その特定の区画に照会するように指示します。
指示 DSS を使ってアプリケーションを最適化するには、複合照会を複数の単純照会に分けてください。たとえば以下の照会では、コーディネーター区画が、区分化キーを複数の値と突き合わせます。この照会を満たすデータは複数の区画にあるため、コーディネーター区画は、照会をすべての区画にブロードキャストします。
SELECT ... FROM t1 WHERE PARTKEY IN (:hostvar1, :hostvar2)
その代わりに、照会を複数の SELECT ステートメント (各 SELECT ステートメントには 1 つのホスト変数がある) に分けます。または、 UNION を指定した 1 つの SELECT ステートメントでこれと同じ結果を得ることもできます。コーディネーター区画はさらに単純な SELECT ステートメントを利用して、指示 DSS を使って必要な区画とだけ通信します。最適化された照会は次のようになります。
SELECT ... AS res1 FROM t1 WHERE PARTKEY=:hostvar1 UNION SELECT ... AS res2 FROM t1 WHERE PARTKEY=:hostvar2
上の手法では、UNION 内 SELECT の数が区画の数よりも大幅に少ない場合に限って、パフォーマンスが向上することに注意してください。
特別な形式の指示 DSS 照会は、コーディネーター区画にあるデータにのみアクセスします。コーディネーター区画は他の区画と通信することなく照会を完了するため、この形式はローカル・バイパス と呼ばれます。
ローカル・バイパスは可能な場合には自動的に使用可能になります。しかし、トランザクションに用いるデータがある区画にトランザクションを経路指定すれば、ローカル・バイパスをより一層活用できます。これを行うための 1 つの手法として、 1 つのリモート・クライアントに各区画との接続を維持させる方法があります。そうすると、トランザクションは、入力区分化キーに基づいた適切な接続を使えるようになります。別の手法として、トランザクションを区画ごとにグループ化して、各区画に対して別個のアプリケーション・サーバーを割り当てる方法もあります。
トランザクション・データのある区画の数を判別するには、 sqlugrpn API (行区画数の入手) を使用します。この API を使うと、アプリケーションは、特定の区分化キーの行の区画数を効果的に計算できます。 sqlugrpn API の詳細については、 管理 API 解説書 を参照してください。
もう 1 つの手法として、 db2atld ユーティリティーを使って入力データを区画数に分け、各区画に対してアプリケーションのコピーを実行する方法があります。 db2atld ユーティリティーの詳細については、 コマンド解説書 を参照してください。
バッファー化挿入は、表待ち行列を利用して、挿入する行をバッファーに蓄積することによって、パフォーマンスを大幅に向上させる挿入ステートメントです。バッファー化挿入を使用するには、アプリケーションは INSERT BUF オプションを使用して準備またはバインドされている必要があります。
バッファー化挿入を行うと、挿入を実行するアプリケーションのパフォーマンスを大幅に向上させることができます。一般に、バッファー化挿入を使用できるのは、単一挿入ステートメント (他のデータベース修正ステートメントはない) をループ内で使用して多数の行を挿入し、データのソースが INSERT ステートメントの VALUES 文節にあるアプリケーションにおいてです。通常、INSERT ステートメントは 1 つ以上のホスト変数を参照し、ループを連続して実行するうちにその値を変更します。 VALUES 文節は、単一行または複数行を指定することができます。
一般的な意思決定支援アプリケーションでは、新規データのロードと定期的な挿入が必要となります。このデータは、膨大な数の行になることがあります。表をロードするときに、バッファー化挿入を使用するアプリケーションを準備しバインドすることができます。
アプリケーションがバッファー化挿入を使用するには、PREP コマンドを使用して、アプリケーション・プログラムのソース・ファイルを処理するか、または生成されたバインド・ファイルに対して BIND コマンドを使用します。いずれの場合も、INSERT BUF オプションを指定する必要があります。アプリケーションのバインドに関する詳細は、バインドを参照してください。アプリケーションの準備に関する詳細は、ソース・ファイルの作成と準備を参照してください。
注: | バッファー化挿入により、以下のステップが生じます。
|
区画環境での標準インターフェース (バッファー化挿入を使用しない) は、次のステップを行って一度に 1 行をロードします (アプリケーションが区画の 1 つでローカルに実行していることを前提とします)。
挿入は、アプリケーションが COMMIT を発行するまでコミットされません。
指定された VALUES 文節のある INSERT ステートメントに対して、 DB2 SQL コンパイラーは、セマンティクス、パフォーマンス、または実現の考慮事項に基づいて、挿入をバッファー化することはできません。 INSERT BUF オプションを使用してアプリケーションを準備またはバインドする場合は、それがバッファー化挿入に従属していないことを確認してください。つまり、
バッファー化挿入によって、次のパフォーマンス上の利点が生じます。
INSERT BUF でバインドするアプリケーションは、バッファー化挿入をクローズするステートメントまたは API が発行される前に、 VALUES 文節を持つ同じ INSERT ステートメントが繰り返されるように作成すべきです。
注: | バッファー化挿入がトランザクション・ログを一杯にするのを防ぐためには、定期的にコミットを実行する必要があります。 |
バッファー化挿入は、アプリケーション・プログラムに影響する可能性のある振る舞いを示します。この振る舞いは、バッファー化挿入の非同期特性により生じます。行の区分化キーの値に基づいて、挿入された各行は、正しい区画を指すバッファーに入れられます。これらのバッファーは、いっぱいになるか、またはフラッシュを引き起こすイベントが発生すると、宛先区画に送信されます。次の事柄に注意して、アプリケーションの設計およびコーディングの際にそれらを考慮に入れる必要があります。
バッファー化 INSERT ステートメントは、オープンまたはクローズのいずれかの状態になっています。ステートメントの最初の呼び出しで、バッファー化 INSERT がオープンし、行が該当するバッファーに追加され、制御がアプリケーションに戻されます。その後の呼び出しでは、行がバッファーに追加され、ステートメントはオープンしたままにされます。ステートメントがオープンしている間、バッファーがその宛先区画に送信されることがあります。その場合、行がターゲット表の区画に挿入されます。バッファー化挿入をクローズするステートメントまたは API が、バッファー化 INSERT ステートメントのオープン中に呼び出された場合 (別の バッファー化 INSERT ステートメントの呼び出しも含む)、またはオープンしているバッファー化 INSERT ステートメントに対して PREPARE ステートメントが発行された場合、オープン・ステートメントは、新規の要求を処理する前に、クローズします。バッファー化 INSERT ステートメントがクローズすると、残ったバッファーはフラッシュされます。次に、行がターゲット区画に送信され、挿入されます。すべてのバッファーが送信されて、すべての行が挿入された後でのみ、新規の要求が処理を開始します。
INSERT ステートメントのクローズ中にエラーが検出されると、新規要求の SQLCA が、エラーの記述でいっぱいになり、新規要求は実行されません。また、そのオープン以来、バッファー化 INSERT ステートメントによって挿入された行のグループ全体が、データベースから除去されます。アプリケーションの状態は、検出された特定のエラーに定義された状態になります。以下はその例です。
たとえば、バッファー化挿入オプションを指定してバインドする、次のアプリケーションを例にして考えてみましょう。
EXEC SQL UPDATE t1 SET COMMENT='about to start inserts'; DO UNTIL EOF OR SQLCODE < 0; READ VALUE OF hv1 FROM A FILE; EXEC SQL INSERT INTO t2 VALUES (:hv1); IF 1000 INSERTS DONE, THEN DO EXEC SQL INSERT INTO t3 VALUES ('another 1000 done'); RESET COUNTER; END; END; EXEC SQL COMMIT;
ファイルに 8 000 個の値が含まれているが、値 3 258 は正しくない (たとえば、固有キー違反) とします。 1 000 個の行を挿入すると、次の SQL ステートメントの実行を引き起こし、 INSERT INTO t2 ステートメントをクローズします。 4 回目に 1 000 個の行の挿入を実行しているときに、値 3 258 のエラーが検出されます。さらに値を挿入した後で (必ずしも次の値とは限らない)、検出されることもあります。この状況では、エラー・コードは INSERT INTO t2 ステートメントに対して戻されます。
また、表 t3 に対して挿入しようとすると、エラーが検出されることもあります。この動作は、INSERT INTO t2 ステートメントをクローズします。この状況では、エラーは表 t2 に適用されるとしても、エラー・コードは INSERT INTO t3 ステートメントに対して戻されます。
代わりに、3 900 行を挿入するとします。行番号 3 258 に関するエラーを通知される前に、アプリケーションはループを終了して、COMMIT を発行しようとします。固有キー違反戻りコードが、COMMIT ステートメントに対して発行され、 COMMIT は実行されません。アプリケーションがデータベース (すなわち遠端) にある 3000 行をコミットするには (EXEC SQL INSERT INTO t3 ... の最後の実行は、それら 3000 行の保管点を終了する)、COMMIT を再発行する必要があります。同様の考慮事項が、ROLLBACK にもあてはまります。
注: | バッファー化挿入を使用する際には、戻される SQLCODES を注意深くモニターして、表が未決状態にならないようにすべきです。たとえば、上の例で THEN DO ステートメントから SQLCODE < 0 文節を除去すると、表は行数が定まらないまま終了してしまいます。 |
次の制限が適用されます。
アプリケーションは、サポートされる任意のクライアント・プラットフォームから実行することができます。
DB2 ユニバーサル・データベースは、並列照会処理のための優れた機能を提供しますが、アプリケーションまたは EXPORT コマンドの接続の単一点は、大量のデータを抽出する際に、障害になる可能性があります。これが生じるのは、データベース・マネージャーからアプリケーションへのデータの受け渡しが、単一ノード (通常はシングル・プロセッサー) 上で実行される CPU 集中処理であるからです。
DB2 ユニバーサル・データベースは、障害を解決するために、プロセッサーの数を増やして、抽出したデータのボリュームが時間単位に正比例するような、いくつかの手段を提供しています。次の例は、これらの手法の背後にある基本的な考えを説明しています。
EMPLOYEE という表があるとします。この表は、20 個のノードに保管されています。正規の部門に属する (すなわち、WORKDEPT はヌルではない) すべての従業員の郵送リスト (FIRSTNME (名)、 LASTNAME (姓)、JOB (担当作業)) を生成するとします。
次の照会は、各ノードで並列に実行し、その後、単一ノード (コーディネーター・ノード) で全体の応答セットを生成します。
SELECT FIRSTNME, LASTNAME, JOB FROM EMPLOYEE WHERE WORKDEPT IS NOT NULL
しかし、次の照会をデータベース内の各区画で実行できます (つまり、区画が 5 つあれば、各区画に 1 つずつ、合計 5 つの別個の照会が必要となる)。それぞれの照会は、照会を実行した特定の区画にレコードがあるすべての従業員名のセットを生成します。それぞれのローカル結果セットは、ファイルにリダイレクトすることができます。その後、結果セットは、単一の結果セットにマージする必要があります。
AIX では、ネットワーク・ファイル・システム (NFS) ファイルの特性を使用して、そのマージを自動化することができます。すべての区画が、応答セットを NFS マウント上の同じファイルに送信する場合、結果はマージされます。応答を大きなバッファーにブロック化せずに NFS を使用すると、パフォーマンスがかなり低下する原因になることに注意してください。
SELECT FIRSTNME, LASTNAME, JOB FROM EMPLOYEE WHERE WORKDEPT IS NOT NULL AND NODENUMBER(NAME) = CURRENT NODE
結果は、ローカル・ファイル (つまり、最終的な結果は 20 個のファイルとなり、それぞれに応答セット全体の一部が含まれる) か、単一の NFS マウント・ファイルのどちらかに保管することができます。
次の例では、2 番目の手法を使用するので、結果は、20 個のノードにわたってマウントされた NFS である単一ファイルに保管されます。 NFS ロック・メカニズムによって、異なる区画から結果ファイルへの書き込みが確実にシリアル化されます。この例は、明示されているように、 NFS ファイル・システムをインストールした AIX プラットフォームでのみ実行されることに注意してください。
#define _POSIX_SOURCE #define INCL_32 #include <stdio.h> #include <stdlib.h> #include <string.h> #include <fcntl.h> #include <sqlenv.h> #include <errno.h> #include <sys/access.h> #include <sys/flock.h> #include <unistd.h> #define BUF_SIZE 1500000 /* Local buffer to store the fetched records */ #define MAX_RECORD_SIZE 80 /* >= size of one written record */ int main(int argc, char *argv[]) { EXEC SQL INCLUDE SQLCA; EXEC SQL BEGIN DECLARE SECTION; char dbname[10]; /* Database name (argument of the program) */ char userid[9]; char passwd[19]; char first_name[21]; char last_name[21]; char job_code[11]; EXEC SQL END DECLARE SECTION; struct flock unlock ; /* structures and variables for handling */ struct flock lock ; /* the NFS locking mechanism */ int lock_command ; int lock_rc ; int iFileHandle ; /* output file */ int iOpenOptions = 0 ; int iPermissions ; char * file_buf ; /* pointer to the buffer where the fetched records are accumulated */ char * write_ptr ; /* position where the next record is written */ int buffer_len = 0 ; /* length of used portion of the buffer */ /* Initialization */ lock.l_type = F_WRLCK; /* An exclusive write lock request */ lock.l_start = 0; /* To lock the entire file */ lock.l_whence = SEEK_SET; lock.l_len = 0; unlock.l_type = F_UNLCK; /* An release lock request */ unlock.l_start = 0; /* To unlock the entire file */ unlock.l_whence = SEEK_SET; unlock.l_len = 0; lock_command = F_SETLKW; /* Set the lock */ iOpenOptions = O_CREAT; /* Create the file if not exist */ iOpenOptions |= O_WRONLY; /* Open for writing only */ /* Connect to the database */ if (argc == 3) { strcpy( dbname, argv[2] ); /* get database name from the argument */ EXEC SQL CONNECT TO :dbname IN SHARE MODE ; if ( SQLCODE != 0 ) { printf( "Error: CONNECT TO the database failed. SQLCODE = %ld\n", SQLCODE ); exit(1); } } else if ( argc == 5 ) { strcpy( dbname, argv[2] ); /* get database name from the argument */ strcpy (userid, argv[3]); strcpy (passwd, argv[4]); EXEC SQL CONNECT TO :dbname IN SHARE MODE USER :userid USING :passwd; if ( SQLCODE != 0 ) { printf( "Error: CONNECT TO the database failed. SQLCODE = %ld\n", SQLCODE ); exit( 1 ); } } else { printf ("\nUSAGE: largevol txt_file database [userid passwd]\n\n"); exit( 1 ) ; } /* endif */ /* Open the input file with the specified access permissions */ if ( ( iFileHandle = open(argv[1], iOpenOptions, 0666 ) ) == -1 ) { printf( "Error: Could not open %s.\n", argv[2] ) ; exit( 2 ) ; } /* Set up error and end of table escapes */ EXEC SQL WHENEVER SQLERROR GO TO ext ; EXEC SQL WHENEVER NOT FOUND GO TO cls ; /* Declare and open the cursor */ EXEC SQL DECLARE c1 CURSOR FOR SELECT firstnme, lastname, job FROM employee WHERE workdept IS NOT NULL AND NODENUMBER(lastname) = CURRENT NODE; EXEC SQL OPEN c1 ; /* Set up the temporary buffer for storing the fetched result */ if ( ( file_buf = ( char * ) malloc( BUF_SIZE ) ) == NULL ) { printf( "Error: Allocation of buffer failed.\n" ) ; exit( 3 ) ; } memset( file_buf, 0, BUF_SIZE ) ; /* reset the buffer */ buffer_len = 0 ; /* reset the buffer length */ write_ptr = file_buf ; /* reset the write pointer */ /* For each fetched record perform the following */ /* - insert it into the buffer following the */ /* previously stored record */ /* - check if there is still enough space in the */ /* buffer for the next record and lock/write/ */ /* unlock the file and initialize the buffer */ /* if not */ do { EXEC SQL FETCH c1 INTO :first_name, :last_name, :job_code; buffer_len += sprintf( write_ptr, "%s %s %s\n", first_name, last_name, job_code ); buffer_len = strlen( file_buf ) ; /* Write the content of the buffer to the file if */ /* the buffer reaches the limit */ if ( buffer_len >= ( BUF_SIZE - MAX_RECORD_SIZE ) ) { /* get excl. write lock */ lock_rc = fcntl(iFileHandle, lock_command, &lock); if ( lock_rc != 0 ) goto file_lock_err; /* position at the end of file */ lock_rc = lseek( iFileHandle, 0, SEEK_END ); if ( lock_rc < 0 ) goto file_seek_err; /* write the buffer */ lock_rc = write( iFileHandle, ( void * ) file_buf, buffer_len ); if ( lock_rc < 0 ) goto file_write_err; /* release the lock */ lock_rc = fcntl( iFileHandle, lock_command, &unlock ); if ( lock_rc != 0 ) goto file_unlock_err; file_buf[0] = '\0' ; /* reset the buffer */ buffer_len = 0 ; /* reset the buffer length */ write_ptr = file_buf ; /* reset the write pointer */ } else { write_ptr = file_buf + buffer_len ; /* next write position */ } } while (1) ; cls: /* Write the last piece of data out to the file */ if (buffer_len > 0) { lock_rc = fcntl(iFileHandle, lock_command, &lock); if (lock_rc != 0) goto file_lock_err; lock_rc = lseek(iFileHandle, 0, SEEK_END); if (lock_rc < 0) goto file_seek_err; lock_rc = write(iFileHandle, (void *)file_buf, buffer_len); if (lock_rc < 0) goto file_write_err; lock_rc = fcntl(iFileHandle, lock_command, &unlock); if (lock_rc != 0) goto file_unlock_err; } free(file_buf); close(iFileHandle); EXEC SQL CLOSE c1; exit (0); ext: if ( SQLCODE != 0 ) printf( "Error: SQLCODE = %ld.\n", SQLCODE ); EXEC SQL WHENEVER SQLERROR CONTINUE; EXEC SQL CONNECT RESET; if ( SQLCODE != 0 ) { printf( "CONNECT RESET Error: SQLCODE = %ld\n", SQLCODE ); exit(4); } exit (5); file_lock_err: printf("Error: file lock error = %ld.\n",lock_rc); /* unconditional unlock of the file */ fcntl(iFileHandle, lock_command, &unlock); exit(6); file_seek_err: printf("Error: file seek error = %ld.\n",lock_rc); /* unconditional unlock of the file */ fcntl(iFileHandle, lock_command, &unlock); exit(7); file_write_err: printf("Error: file write error = %ld.\n",lock_rc); /* unconditional unlock of the file */ fcntl(iFileHandle, lock_command, &unlock); exit(8); file_unlock_err: printf("Error: file unlock error = %ld.\n",lock_rc); /* unconditional unlock of the file */ fcntl(iFileHandle, lock_command, &unlock); exit(9); }
この手法は、単一表からの選択だけでなく、さらに複雑な照会にも適用されます。ただし、照会が配列されていない操作を必要とする (つまり、 Explain がコーディネーター・サブセクションの他に複数のサブセクションを表示する) 場合に、照会をすべての区画で並行して実行すると、いくつかの区画であまりにも多くのプロセスが発生することになります。この状況では、必要なだけの区画で照会の結果を一時表 TEMP に保管した後、最終的な抽出を TEMP から並行して実行できます。
選択した担当作業種別のみにしたがって、すべての従業員を抽出する場合は、次のようにして、FIRSTNME、LASTNAME、JOB という名前の列のある TEMP 表を定義することができます。
INSERT INTO TEMP SELECT FIRSTNME, LASTNAME, JOB FROM EMPLOYEE WHERE WORKDEPT IS NOT NULL AND EMPNO NOT IN (SELECT EMPNO FROM EMP_ACT WHERE EMPNO<200)
次いで、TEMP に対して並列抽出を実行します。
TEMP 表を定義するときには、次の事柄を考慮します。
最終的な応答セット (すべてのノードからマージされる部分的な応答セット) を分類する必要がある場合は、次のようにすることができます。