接続プールとは、いくつかの接続間で再使用できる、データベースへの物理接続グループです。接続プーリング機能の目的は、それぞれの接続に専用接続を使用させないことでパフォーマンスを改善し、リソースの使用を削減することです。
図11-4は、接続プーリングの機能を示します。この例では、アプリケーションの4つのスレッドが接続プールを使用してデータベースと対話しています。この接続プールには物理接続が2つあります。異なるランタイム・コンテキストを使用する4つのスレッドにより、接続プール・ハンドルが使用されます。
図11-4 接続プーリング
thread1() { EXEC SQL CONTEXT ALLOCATE :ctx1; EXEC SQL CONTEXT USE:ctxl; EXEC SQL CONNECT :uid AT :TC1 USING :db_string; ... } thread2() { EXEC SQL CONTEXT ALLOCATE :ctx2; EXEC SQL CONNECT :uid AT :TC2 USING :db_string; ... } thread3() { EXEC SQL CONTEXT ALLOCATE :ctx3; EXEC SQL CONNECT :uid AT :TC3 USING :db_string; EXEC SQL AT :TC3 SELECT count(*) into :count FROM emp; ... } thread4() { EXEC SQL CONTEXT ALLOCATE :ctx4; EXEC SQL CONNECT :uid AT :TC4 USING :db_string; ... }
この例で、TC1、TC2、TC3およびTC4という4つの名前付き接続は、それぞれT1、T2、T3およびT4というスレッドにより作成された仮想接続です。異なるランタイム・コンテキストからの名前付き接続TC1、TC2、TC3およびTC4が1つの接続プールを共有し、接続プール内で使用可能な物理データベース接続を共有します。C1およびC2という2つの物理接続が、4つの名前付き接続にサービスを提供し、同じ1つのデータベースに接続します。
スレッドT1から最初の接続要求TC1を受け取ると、SQLLIBはデータベースへの物理接続C1を1つ含む接続プールを作成します。スレッドT2から別の接続要求TC2が同一データベースに送信されると、C1が空いている場合はデータベースへのTC2要求に対してC1がサービスを提供します。C1が空いていない場合は、要求にサービスを提供するために新しい物理接続C2が作成されます。スレッドT3からTC3という別の接続要求が受信された場合、物理接続C1とC2がどちらも使用中の場合は、TC3は指定された時間待機するか、エラー・メッセージを返します。
スレッドT2が名前付き接続TC2を使用してデータを選択する必要があるときは、物理接続C1またはC2の空いている方を取得します。要求に対してサービスが提供された後、選択されていた接続は接続プール内で再び使用可能になるため、別の名前付き接続または仮想接続がその物理接続を利用できます。
この項には、次の項目が含まれます。
アプリケーションのプリコンパイル中に接続プーリングを使用可能にするには、CPOOL=YES
コマンドライン・オプションを設定する必要があります。CPOOL=YES/NO
の設定に基づいて、接続プーリング機能が使用可能または使用禁止になります。
注意:
デフォルトでは、CPOOL
はNO
に設定されます。つまり、接続プーリング機能は使用禁止です。この機能をインラインで使用可能または禁止にすることはできません。
CPOOL
がYES
に設定されていても、外部オペレーティング・システム認証では接続プールは作成されません。
表11-1に、接続プーリングのコマンドライン・オプションの一覧を示します。
表11-1 接続プーリングのコマンドライン・オプション
オプション | 有効な値 | デフォルト | 備考 |
---|---|---|---|
CPOOL |
YESまたはNO |
NO |
このオプションに基づき、プリコンパイラでは、SQLLIBに接続プール機能を有効または無効にするように指示する適切なコードを生成します。 注意: このオプションがNOに設定されている場合、プリコンパイラはその他の接続プーリング・オプションを無視します。 |
CMAX |
有効値は1から65535です。 |
100 |
データベースに対してオープンできる物理接続の最大数を指定します。CMAX値は、CMIN+CINCRより大きく設定する必要があります。 注意: この値に達すると、物理接続をそれ以上オープンできません。 通常のアプリケーションでは、同時データベース操作を100個実行するように設定すれば十分です。ユーザーが適切な値を設定できます。 |
CMIN |
有効値は1から(CMAX-CINCR)です。 |
2 |
接続プール内の最小物理接続数を指定します。最初は、CMINにより指定されたとおりに物理接続がすべてサーバーに対してオープンされます。それ以降は、必要な場合にのみ物理接続がオープンされます。パフォーマンスを最適にするには、CMINを、アプリケーションによる実行が計画または予想される同時実行文の合計数に設定する必要があります。デフォルト値は2に設定されます。 |
CINCR |
有効値は1から(CMAX-CMIN)です。 |
1 |
現在の物理接続数がCMAX値より少ない場合、データベースに対してオープンされる物理接続数の次の増分をアプリケーションで設定できるようにします。不要な数の接続が作成されないように、デフォルト値は1に設定されています。 |
CTIMEOUT |
有効値は1から65535です。 |
0(未設定)。つまり、タイムアウトになりません。 |
指定された期間(秒単位)より長い間アイドル状態になっている物理接続を終了し、オープンされている物理接続数を最適に保ちます。この属性を設定しない場合、物理接続はタイムアウトしません。このため、接続プールが終了するまで物理接続はクローズしません。 注意: 物理接続を新しく作成すると、サーバーへのラウンドトリップが必要になります。 |
CNOWAIT |
有効値は1から65535です。 |
0(未設定)。つまり、接続が空くのを待機します。 |
この属性は、プール内の他のすべての物理接続が使用中で、物理接続の合計数がすでに最大値に達している場合に、アプリケーションで繰り返し物理接続を要求する必要があるかどうかを決定します。物理接続が使用できず、これ以上物理接続をオープンできない場合、この属性が設定されているとエラーが発生します。そうでない場合、コールは別の接続が取得されるまで待機します。デフォルトでは、CNOWAITは設定されないため、スレッドは、エラーを戻すかわりに、空いている接続を取得できるまで待機します。 |
通常のマルチスレッド・アプリケーションでは、n個の物理接続を持つプールを作成します。nの値は、プリコンパイル中にCMIN値で指定する必要があります。最初の接続コールでは、データベースへの最小数(CMIN)の物理接続が作成されます。新しい要求に対しては、仮想接続(名前付き接続)から物理接続へのマッピングが実行されます。これを次の項で説明します。
ケース1: 物理接続が(すでにオープンされている接続の中で)使用可能な場合、新しい要求にはこの接続がサービスを提供します。
ケース2: 物理接続がすべて使用中の場合
ケース2a: オープンされている接続の数が最大数制限(CMAX)に達していない場合は、CINCR分の新しい接続が作成され、その中の1つが要求の処理に使用されます。
ケース2b: オープンされている物理接続数が最大数(CMAX)に達していて、CNOWAITが設定されていない場合、要求は接続を取得できるまで待機します。それ以外の場合は、「ORA 24401: これ以上の接続は開けません」というエラー・メッセージが表示されます。
次の例の説明は、図11-4を参照してください。
Let CMIN be 1, CMAX be 2, and CINCR be 1.
次のシナリオについて考えます。最初の要求TC1を受け取ると、SQLLIBは物理接続C1を1つ含む接続プールを作成します。別の要求TC2を受け取ると、アプリケーションはC1が空いているかどうかをチェックします。C1は最初の要求の処理に使用されているため(ケース1)、要求にサービスを提供するために新しい物理接続C2が作成されます(ケース2a)。別の要求TC3を受け取ったときに、C1もC2も使用中の場合、TC3は指定された時間待機するか、エラー・メッセージ付きで戻されます(ケース2b)。
パフォーマンスを向上するために、アプリケーションに応じて接続プーリングのパラメータを設定できます。図11-5のパフォーマンス・グラフは、Pro*C/C++のデモ・プログラム:1のCMIN値を変更することで、パフォーマンスが向上することを示しています。デモ・プログラム:2は、CMAXパラメータを変更することで、パフォーマンスが向上することを示しています。
デモ・プログラム: 1のプリコンパイル中には、次の接続プール・パラメータが使用されます。
CMAX = 40 CINCR = 3 CMIN = varying values between 1 to 40 CPOOL = YES CTIMEOUT - Do not set
(物理接続がタイムアウトしないことを示します。)
CNOWAIT - Do not set
(空いている接続を取得するまでスレッドが待機することを示します。詳細は、表11-1を参照してください。)
この例に必要なその他のコマンドライン・オプションは、次の項で示します。
threads = yes
注意:
この例では、スレッド数は40で、データベース操作はローカル・データベースに対して実行されます。
CPOOL=NO(接続プーリングを使用しない)を指定した場合、アプリケーションの所要時間は6.1秒でした。これに対し、CPOOL=YES(接続プーリングを使用する)を指定すると、アプリケーションの最小所要時間は1.3秒になりました(CMIN=2の場合)。
どちらの場合も、接続プールによって短縮されるのはCONNECT文の所要時間のみであるため、データベース問合せ操作に要する時間は変わりません。CPOOL=NOを指定した場合、アプリケーションは40個の専用接続を作成します。CPOOL=YESとCMIN=2を指定した場合は、最初に2つの接続を作成し、2つのスレッドが接続に同時にアクセスする場合にのみ、これ以上の接続を作成します。そうでない場合は、すべてのスレッドがこの2つの接続を共有します。このため、アプリケーションでは潜在的に38個の接続が回避され、これにより、接続確立のためのサーバーへのラウンドトリップが38個回避されます。その結果、パフォーマンスは3倍に向上します。
注意:
前述の結果は、Solaris 2.6オペレーティング・システム上でOracleサーバーが1つ稼働する、単一CPUおよび256MB RAM搭載のSparc Ultra60マシンでの測定値です。サーバーとクライアントは同一マシン上で実行されています。
図11-5 パフォーマンス・グラフ
CPOOL=YESの線は、接続プーリングが使用可能になっているときのアプリケーションの所要時間を表します。CPOOL=NOの線は、接続プーリングが使用禁止になっているときのアプリケーションの所要時間を表します。
/* * cpdemo1.pc * * Description: * The program creates as many sessions as there are threads. * Each thread connects to the default database and executes the * SELECT statement 5 times. Each thread has its own runtime context. * */ #include <stdio.h> #include <stdlib.h> #include <string.h> #include <sqlca.h> #define _EXC_OS_ _EXC__UNIX #define _CMA_OS_ _CMA__UNIX #ifdef DCE_THREADS #include <pthread.h> #else #include <pthread.h> typedef void* pthread_addr_t; typedef void* (*pthread_startroutine_t) (void*); #define pthread_attr_default (const pthread_attr_t *)NULL #endif /* Function prototypes */ void err_report(); void do_transaction(); void get_transaction(); void logon(); void logoff(); #define CONNINFO "hr/hr" #define THREADS 40 struct parameters { sql_context * ctx; int thread_id; }; typedef struct parameters parameters; struct timeval tp1; struct timeval tp2; /*************************************** * Main ***************************************/ main() { sql_context ctx[THREADS]; pthread_t thread_id[THREADS]; pthread_addr_t status; parameters params[THREADS]; int i; EXEC SQL ENABLE THREADS; EXEC SQL WHENEVER SQLERROR DO err_report(sqlca); if(gettimeofday(&tp1, (void*)NULL) == -1) { perror("First: "); exit(0); } /* Create THREADS sessions by connecting THREADS times */ for(i=0;i<THREADS;i++) { printf("Start Session %d....",i); EXEC SQL CONTEXT ALLOCATE :ctx[i]; logon(ctx[i],CONNINFO); } /*Spawn threads*/ for(i=0;i<THREADS;i++) { params[i].ctx=ctx[i]; params[i].thread_id=i; if (pthread_create(&thread_id[i],pthread_attr_default, (pthread_startroutine_t)do_transaction, (pthread_addr_t) ¶ms[i])) printf("Cant create thread %d\n",i); else printf("Created Thread %d\n", i); } /* Logoff sessions....*/ for(i=0;i<THREADS;i++) { /*wait for thread to end */ if (pthread_join(thread_id[i],&status)) printf("Error when waiting for thread % to terminate\n", i); else printf("stopped\n"); if(i==THREADS-1) { logoff(ctx[i]); EXEC SQL CONTEXT FREE :ctx[i]; } } if(gettimeofday(&tp2, (void*)NULL) == -1) { perror("Second: "); exit(0); } printf(" \n\nTHE TOTAL TIME TAKEN FOR THE PROGRAM EXECUTION = %f \n\n", (float)(tp2.tv_sec - tp1.tv_sec) + ((float)(tp2.tv_usec - tp1.tv_usec)/1000000.0)); } /*********************************************************************** * Function: do_transaction * Description: This functions executes SELECT 5 times and calls COMMIT. ***********************************************************************/ void do_transaction(params) parameters *params; { struct sqlca sqlca; int src_count; sql_context ctx=params->ctx; EXEC SQL WHENEVER SQLERROR DO err_report(sqlca); EXEC SQL CONTEXT USE :ctx; printf("Thread %d executing transaction\n",params->thread_id); EXEC SQL COMMIT; EXEC SQL SELECT count(*) into :src_count from EMPLOYEES; EXEC SQL SELECT count(*) into :src_count from EMPLOYEES; EXEC SQL SELECT count(*) into :src_count from EMPLOYEES; EXEC SQL SELECT count(*) into :src_count from EMPLOYEES; EXEC SQL SELECT count(*) into :src_count from EMPLOYEES; } /************************************************************** * Function: err_report * Description: This routine prints out the most recent error **************************************************************/ void err_report(sqlca) struct sqlca sqlca; { if (sqlca.sqlcode < 0) printf("\n%.*s\n\n",sqlca.sqlerrm.sqlerrml,sqlca.sqlerrm.sqlerrmc); exit(1); } /************************************************************ * Function: logon * Description: Logs on to the database as USERNAME/PASSWORD ************************************************************/ void logon(ctx,connect_info) sql_context ctx; char * connect_info; { EXEC SQL WHENEVER SQLERROR DO err_report(sqlca); EXEC SQL CONTEXT USE :ctx; EXEC SQL CONNECT :connect_info; printf("Connected!\n"); } /*************************************************** * Function: logoff * Description: This routine logs off the database ***************************************************/ void logoff(ctx) sql_context ctx; { EXEC SQL WHENEVER SQLERROR DO err_report(sqlca); EXEC SQL CONTEXT USE :ctx; EXEC SQL COMMIT WORK RELEASE; printf("Logged off!\n"); }
デモ・プログラム: 2のプリコンパイル中に、次の接続プーリング・パラメータが使用されます。
CMAX = 5から40の可変値
CINCR = 3
CMIN = 1から40の可変値
CPOOL = YES
CTIMEOUT-設定しない
(物理接続がタイムアウトしないことを示します。)
CNOWAIT-設定しない
(空いている接続を取得するまでスレッドが待機することを示します。詳細は、表11-1を参照してください。)
この例に必要なその他のコマンドライン・オプションは、次の項で示します。
threads = yes
次の図は、cpdemo2のパフォーマンス・グラフを示します。
注意:
この例では、スレッド数は40で、データベース操作はローカル・データベースに対して実行されます。
この例では、CMIN=5、CMAX=14のときに、CPOOL=NOを使用した場合と比べてプログラムの実行速度が約2.3倍に向上し、最善のパフォーマンスが得られます。ただし、接続プーリングを使用可能にすることで実行がさらに高速化する「cpdemo1」ほどの向上は見られません。これは、「cpdemo1」では単純なSELECT文のみを実行しているのに対し、「cpdemo2」ではUPDATE文とSELECT文の両方を実行しているためです。したがって、「cpdemo1」ではデータベース操作の実行よりも接続の作成に時間がかかっています。接続プーリングを使用可能にすると、作成される接続の数が減り、時間が短縮されます。その結果、全体的なパフォーマンスが向上します。「cpdemo2」では、データベース操作の実行に比べて接続の作成にかかる時間が少ないため、全体的なパフォーマンス向上の度合いは低くなります。
次のグラフでは、CPOOL=YESの線は、接続プーリングが使用可能になっているときのアプリケーションの所要時間を表します。CPOOL=NOの線は、接続プーリングが使用禁止になっているときのアプリケーションの所要時間を表します。デモ・プログラム「cpdemo2」では40個のスレッドを作成します。CPOOL=NOオプションの場合は、スレッドはそれぞれサーバーに対する専用接続を確立します。このため、40個の接続が作成されます。CPOOL=YESとCMAX=14を指定して同じデモ・プログラムをビルドすると、最大14の接続が作成されます。これらの接続は40個のスレッドで共有されるため、少なくとも26個の接続が節約され、サーバーへの26回のラウンドトリップが回避されます。
次の2つのグラフは、CMINとCMAXの値をそれぞれ変化させた場合のパフォーマンスを示しています。
図11-6 ケース1のパフォーマンス・グラフ
CPOOL=NOの場合、アプリケーションでは実行に約7.5秒かかります。CPOOL=YESで、CMIN=8およびCMAX=14の場合、実行時間は4.5秒に短縮されます。このため、パフォーマンスの改善は約1.7倍になります。このパフォーマンスの相違は、データベース操作が違う(SELECT対UPDATE)のためです。これは純粋にサーバー側のアクティビティであり、クライアント側機能である接続プール機能の範囲外です。
図11-7 ケース2のパフォーマンス・グラフ
前述のグラフの場合、デモ・プログラムはCMIN=5およびCINCR=3で実行されました。最善のパフォーマンスはCMAX=14の場合に得られます。CPOOL=NOの場合は実行に約7.4秒かかっています。CPOOL=YESでCMAX=14の場合、実行時間は約3.1秒まで短縮され、パフォーマンスは2.3倍向上しています。
パフォーマンス向上の度合いはCMAXによって異なります。したがって、特定のアプリケーションで最善のパフォーマンスを得るには、最適なパフォーマンスに達するまでCMINとCMAXを変化させる必要があります。
/* * cpdemo2.pc * Program to show the performance imcrement when the cpool option is used * Run this program with cpool=no. Record the time taken for the program to * execute * * Compare the execution time * * This program also demonstrates the impact of a properly tuned CMAX * parameter on the performance * * Run the program with the following parameter values * * CMIN=5 * CINCR=2 * CMAX=20 * */ #include <stdio.h> #include <sqlca.h> #ifdef DCE_THREADS #include <pthread.h> #else #include <sys/time.h> #include <pthread.h> typedef void* pthread_addr_t; typedef void* (*pthread_startroutine_t) (void*); #define pthread_attr_default (const pthread_attr_t *)NULL #endif #define CONNINFO "hr/hr" #define THREADS 40 /***** prototypes ************** */ void selectFunction(); void updateFunction(); void err_report(struct sqlca sqlca); /* ************************* */ /***** parameter to the function selectFunction, updateFunction */ struct parameters { sql_context ctx; char connName[20]; char dbName[20]; int thread_id; }; typedef struct parameters parameters; /*******************************************/ parameters params[THREADS]; struct timeval tp1; struct timeval tp2; int main() { int i; pthread_t thread_id[THREADS]; pthread_addr_t status; int thrNos[THREADS]; for(i=0; i<THREADS; i++) thrNos[i] = i; EXEC SQL ENABLE THREADS; /* Time before executing the program */ if(gettimeofday(&tp1, (void*)NULL) == -1){ perror("First: "); exit(0); } EXEC SQL WHENEVER SQLERROR DO err_report(sqlca); /* connect THREADS times to the data base */ for(i=0; i<THREADS; i++) { strcpy(params[i].dbName, ""); sprintf(params[i].connName,"conn%d", i); params[i].thread_id = i; /* logon to the data base */ EXEC SQL CONTEXT ALLOCATE :params[i].ctx; EXEC SQL CONTEXT USE :params[i].ctx; EXEC SQL CONNECT :CONNINFO AT :params[i].connName USING :params[i].dbName; } /* create THREADS number of threads */ for(i=0;i<THREADS;i++) { printf("Creating thread %d \n", i); if(i%2) { /* do a select operation if the thread id is odd */ if(pthread_create(&thread_id[i],pthread_attr_default, (pthread_startroutine_t)selectFunction, (pthread_addr_t) ¶ms[i])) printf("Cant create thread %d \n", i); } else { /* otherwise do an update operation */ if(pthread_create(&thread_id[i],pthread_attr_default, (pthread_startroutine_t)updateFunction, (pthread_addr_t) ¶ms[i])) printf("Cant create thread %d \n", i); } } for(i=0; i<THREADS; i++) { if(pthread_join(thread_id[i],&status)) printf("Error when waiting for thread % to terminate\n", i); } if(gettimeofday(&tp2, (void*)NULL) == -1){ perror("Second: "); exit(0); } printf(" \n\nTHE TOTAL TIME TAKEN FOR THE PROGRAM EXECUTION = %f \n\n", (float)(tp2.tv_sec - tp1.tv_sec) + ((float)(tp2.tv_usec - tp1.tv_usec)/1000000.0)); /* free the context */ for(i=0; i<THREADS; i++) { EXEC SQL CONTEXT USE :params[i].ctx; EXEC SQL AT :params[i].connName COMMIT WORK RELEASE; EXEC SQL CONTEXT FREE :params[i].ctx; } return 0; } void selectFunction(parameters *params) { struct sqlca sqlca; char empName[110][21]; printf("Thread %d selecting .... \n", params->thread_id); EXEC SQL CONTEXT USE :params->ctx; EXEC SQL AT : params->connName SELECT FIRST_NAME into empName from EMPLOYEES; printf("Thread %d selected ....\n", params->thread_id); return 0; } void updateFunction(parameters *params) { struct sqlca sqlca; printf(" Thread %d Updating ... \n", params->thread_id); EXEC SQL CONTEXT USE :params->ctx; EXEC SQL AT :params->connName update EMPLOYEES set SALARY = 4000 where DEPARTMENT_ID = 10; /* commit the changes */ EXEC SQL AT :params->connName COMMIT; printf(" Thread %d Updated ... \n", params->thread_id); return 0; } /*********** Oracle error ***********/ void err_report(struct sqlca sqlca) { if (sqlca.sqlcode < 0) printf("\n%.*s\n\n",sqlca.sqlerrm.sqlerrml,sqlca.sqlerrm.sqlerrmc); exit(0); }