連続問合せ通知(CQN)を使用すると、問合せをオブジェクト変更通知用(デフォルト)または問合せ結果変更通知用としてアプリケーションからデータベースに登録できます。登録済の問合せで参照されるオブジェクトは、登録済オブジェクトです。
オブジェクト変更通知(OCN)に対する問合せが登録されると、その問合せで参照されるオブジェクトがトランザクションによって変更されてコミットされるたびに、問合せ結果が変更されたかどうかにかかわらず、データベースからアプリケーションに通知されます。
問合せ結果変更通知(QRCN)に対して問合せが登録されると、その問合せの結果がトランザクションによって変更されてコミットされるたびに、データベースからアプリケーションに通知されます。
CQN登録により、1つ以上の問合せのリストが通知タイプ(OCNまたはQRCN)および通知ハンドラに関連付けられます。CQN登録を作成するには、PL/SQLインタフェースまたはOracle Call Interface(OCI)を使用できます。PL/SQLインタフェースを使用する場合、通知ハンドラはサーバー側のPL/SQLストアド・プロシージャになり、OCIを使用する場合、通知ハンドラはクライアント側のCコールバック・プロシージャになります。
この章では、CQNの一般的な概念と、PL/SQL CQNインタフェースの使用方法について説明します。CQNにOCIを使用する方法の詳細は、『Oracle Call Interfaceプログラマーズ・ガイド』を参照してください。
内容は次のとおりです。
注意: OCNおよびQRCNという用語は、通知タイプと通知自体の両方を指します。アプリケーションによりOCNに対する問合せが登録されると、データベースからアプリケーションにOCNが送信され、アプリケーションによりQRCNに対する問合せが登録されると、データベースからアプリケーションにQRCNが送信されます。 |
アプリケーションでオブジェクト変更通知(OCN)に対する問合せが登録されると、その問合せに関連付けられているオブジェクトがトランザクションによって変更されてコミットされるたびに、問合せ結果が変更されたかどうかにかかわらず、データベースからアプリケーションにOCNが送信されます。
たとえば、アプリケーションで例15-1のOCNに対する問合せが登録され、EMPLOYEES
表を変更するトランザクションがユーザーによってコミットされると、変更された行が問合せの条件句を満たしていない場合でも(DEPARTMENT_ID
= 5の場合など)、データベースからアプリケーションにOCNが送信されます。
注意: QRCNを使用するには、データベースのCOMPATIBLE 初期化パラメータが11.0.0以上であり、自動UNDO管理(AUM)が有効(デフォルト設定)である必要があります。
AUMの詳細は、『Oracle Database管理者ガイド』を参照してください。 |
アプリケーションで問合せ結果変更通知(QRCN)に対する問合せが登録されると、その問合せの結果がトランザクションによって変更されてコミットされるたびに、データベースからアプリケーションにQRCNが送信されます。
たとえば、アプリケーションで例15-1のQRCNに対する問合せが登録されると、問合せ結果セットが変更された場合のみ、つまり、次のデータ操作言語(DML)文のいずれかがコミットされた場合にのみ、データベースからアプリケーションにQRCNが送信されます。
問合せの条件句(DEPARTMENT_ID
= 10)を満たす行のINSERT
またはDELETE
。
問合せの条件句(DEPARTMENT_ID
= 10)を満たしている行のEMPLOYEE_ID
またはSALARY
列へのUPDATE
。
行の値が10から10以外の値に変更され、その行が結果セットから削除される原因となったDEPARTMENT_ID
列へのUPDATE
。
行の値が10以外の値から10に変更され、その行が結果セットに追加される原因となったDEPARTMENT_ID
列へのUPDATE
。
デフォルトの通知タイプはOCNです。QRCNの場合は、CQ_NOTIFICATION$_REG_INFO
オブジェクトのQOSFLAGS
属性にQOS_QUERY
を指定します。
QRCNでは、保証モード(デフォルト)またはベストエフォート・モードを選択できます。
内容は次のとおりです。
保証モードでは、誤検出はありません。つまり、問合せ結果セットの変更が保証される場合にのみ、データベースからアプリケーションにQRCNが送信されます。
たとえば、アプリケーションで例15-1のQRCNに対する問合せが登録され、従業員201が部門10に所属しており、次の文が実行されるとします。
UPDATE EMPLOYEES SET SALARY = SALARY + 10 WHERE EMPLOYEE_ID = 201; UPDATE EMPLOYEES SET SALARY = SALARY - 10 WHERE EMPLOYEE_ID = 201; COMMIT;
このトランザクションの各UPDATE
文によって問合せ結果セットは変更されますが、各文を合計すると問合せ結果セットへの影響はないため、このトランザクションに対してはデータベースからアプリケーションにQRCNは送信されません。
保証モードでは、CQ_NOTIFICATION$_REG_INFO
オブジェクトのQOSFLAGS
属性にQOS_BEST_EFFORT
ではなくQOS_QUERY
を指定します。
問合せの中には、保証モードのQRCNに対して複雑すぎるものがあります。保証モードで登録可能な問合せの特性は、15.7.5.1項を参照してください。
保証モードでは複雑すぎる問合せの中には、ベストエフォート・モードでQRCNに対して登録できるものがあります。このモードでは、簡略バージョンの問合せがCQNによって作成および登録されます。
たとえば、例15-2の問合せは、集計ファンクションSUM
を含んでおり、保証モードのQRCNに対しては複雑すぎます。
ベストエフォート・モードでは、例15-2の問合せの次のような簡略バージョンがCQNによって登録されます。
SELECT SALARY
FROM EMPLOYEES
WHERE DEPARTMENT_ID = 20;
元の問合せの結果が変更されるたびに、その簡略バージョンの結果も変更されるため、簡略化が原因で通知が失われることはありません。ただし、簡略バージョンの結果は元の問合せの結果が変更されなくても変更される場合があるため、簡略化によって誤検出が発生することがあります。
ベストエフォート・モードでは、データベースは次の処理を行います。
通知関連の処理によるOLTPレスポンスのオーバーヘッドを次のように最小化します。
単一表問合せの場合、データベースでは、どの列が変更されたか、および変更された行によってどの条件句が満たされたかに基づいて、問合せ結果が変更されたかどうかを判断します。
複数表問合せ(結合)の場合、データベースでは、複数の表の間の主キー/外部キー制約関係を使用して、問合せ結果が変更されたかどうかを判断します。
DML文によって問合せ結果セットが変更されるたびに、最初のDML文によって行われた変更が後続のDML文によって無効になった場合でも、アプリケーションにQRCNを送信します。
ベストエフォート・モードでのオーバーヘッドを最小化により、CQNで簡略化されない問合せに対しても、誤検出がまれに発生します。たとえば、例15-1の問合せと、15.2.1項のトランザクションを想定します。ベストエフォート・モードでは、CQNによって問合せは簡略化されませんが、トランザクションによって誤検出が発生します。
一部のタイプの問合せは非常に簡略化されているため、無効化がオブジェクト・レベルで発生します。つまり、該当の問合せで参照されるいずれかのオブジェクトが変更されるたびに発生します。このような問合せの例としては、サポートされていない列型を使用する問合せや、副問合せを含んでいる問合せがあります。この問題を解決するには、元の問合せを書き換えます。
たとえば、例15-3の問合せは、副問合せを含んでいるため、保証モードのQRCNに対しては複雑すぎます。
例15-3 簡略バージョンによってオブジェクトが無効になる問合せ
SELECT SALARY FROM EMPLOYEES WHERE DEPARTMENT_ID IN ( SELECT DEPARTMENT_ID FROM DEPARTMENTS WHERE LOCATION_ID = 1700 );
ベストエフォート・モードでは、例15-3の問合せがCQNによって次のように簡略化されます。
SELECT * FROM EMPLOYEES, DEPARTMENTS;
問合せの簡略化により、オブジェクトが無効になる場合があります。ただし、元の問合せを次のように書き換えると、その問合せを保証モードまたはベストエフォート・モードで登録できます。
SELECT SALARY FROM EMPLOYEES, DEPARTMENTS WHERE EMPLOYEES.DEPARTMENT_ID = DEPARTMENTS.DEPARTMENT_ID AND DEPARTMENTS.LOCATION_ID = 1700;
ベストエフォート・モードでのみ登録可能な問合せについては、15.7.5.2項で説明します。
デフォルトのQRCNモードは保証モードです。ベストエフォート・モードの場合は、CQ_NOTIFICATION$_REG_INFO
オブジェクトのQOSFLAGS
属性にQOS_BEST_EFFORT
を指定します。
通知を生成するイベントは次のとおりです。
通知タイプがOCNの場合、1つ以上の登録済オブジェクトを変更するあらゆるDMLトランザクションで、コミット時に各オブジェクトに対して通知が1つ生成されます。
通知タイプがQRCNの場合、1つ以上の登録済問合せの結果を変更するあらゆるDMLトランザクションで、コミット時に通知が生成されます。この通知には、結果が変更された問合せのIDが含まれます。
どちらの通知タイプでも、通知には次の内容が含まれます。
変更された各表の名前
操作タイプ(INSERT
、UPDATE
またはDELETE
)
変更された各行のROWID
(ROWID
オプションを使用して登録が作成され、変更された行の数があまり多くない場合)。詳細は、第15.7.2.3項を参照してください。
OCNとQRCNの両方について、次のデータ定義言語(DDL)文により、コミット時に通知が生成されます。
ALTER
TABLE
TRUNCATE
TABLE
FLASHBACK
TABLE
DROP
TABLE
注意: 通知タイプがOCNの場合、DROP TABLE 文のコミットによってDROP NOTIFICATION が生成されます。
削除された表に対する問合せのOCN登録は、その表(すでに存在しない表)から関連付けが解除されますが、登録自体は存続します。これらの登録のいずれかが削除された表以外のオブジェクトに関連付けられている場合、それらのオブジェクトに対する変更がコミットされると、通知は引き続き生成されます。削除された表にのみ関連付けられていた登録も存続し、その作成者は問合せ(およびその参照オブジェクト)をその登録に追加できます。 OCN登録は、問合せの登録時点におけるオブジェクトのバージョンと定義に基づきます。オブジェクトが削除されると、そのオブジェクトの登録の関連付けはオブジェクトから永続的に解除されます。オブジェクトが削除されたオブジェクトと同じ名前および同じスキーマで作成された場合、作成されたオブジェクトは削除されたオブジェクトに関連付けられていたOCN登録には関連付けられません。 |
通知タイプがQRCNの場合、次のようになります。
通知には次の内容が含まれます。
結果が変更された問合せのID
変更された表の名前
DDL操作のタイプ
登録済問合せを無効にするDDL操作の中には、その問合せを登録解除する原因となるものもあります。
たとえば、次の問合せがQRCNに対して登録されるとします。
SELECT COL1 FROM TEST_TABLE WHERE COL2 = 1;
TEST_TABLE
のスキーマは次のとおりとします。
(COL1 NUMBER, COL2 NUMBER, COL3 NUMBER)
この場合、次のDDL文により、コミット時に問合せが無効になり、問合せが登録から削除されます。
ALTER TABLE DROP COLUMN COL2;
OCNとQRCNの両方について、登録解除(データベースからの登録の削除)によって通知が生成されます。データベースで登録が削除される原因は次のとおりです。
タイムアウト
問合せの登録時にTIMEOUT
がゼロ以外の値で指定された場合、指定の時間間隔後にデータベースによって登録がパージされます。
問合せの登録時にQOS_DEREG_NFY
が指定された場合、最初の通知の生成後にデータベースによって登録がパージされます。
権限の消失
登録済問合せに関連付けられているオブジェクトに対する権限が消失し、通知タイプがOCNの場合、データベースによって登録がパージされます。(通知タイプがQRCNの場合は、データベースによってその問合せは登録から削除されますが、登録はパージされません。)
問合せの登録に必要な権限は、15.7.3項を参照してください。
クライアント・アプリケーションによる明示的な登録解除の実行時には、通知は生成されません。
グローバル・イベントEVENT_STARTUP
およびEVENT_SHUTDOWN
により、通知が生成されます。
Oracle RAC環境では、次のイベントによって通知が生成されます。
EVENT_STARTUP
(データベースの最初のインスタンスの起動時)
EVENT_SHUTDOWN
(データベースの最終インスタンスの停止時)
EVENT_SHUTDOWN_ANY
(データベースのいずれかのインスタンスの停止時)
前述のグローバル・イベントは、DBMS_CQ_NOTIFICATION
パッケージで定義されている定数です。
参照: DBMS_CQ_NOTIFICATION パッケージの詳細は、『Oracle Database PL/SQLパッケージ・プロシージャおよびタイプ・リファレンス』を参照してください。 |
通知には、次の情報の一部またはすべてが含まれます。
イベントのタイプ(次のいずれか)
起動
オブジェクト変更
問合せ結果変更
登録解除
停止
影響を受けた登録の登録ID
変更されたオブジェクトの名前
ROWID
オプションが指定されている場合の、変更された行のROWID
通知タイプがQRCNの場合の、結果が変更された問合せの問合せID
通知がDMLまたはDDL文の結果として生成された場合
変更された表の名前の配列
操作タイプ(INSERT
、UPDATE
など)
通知には、変更されたデータ自体は含まれません。たとえば、5000から6000への月給の増額があっても、通知には示されません。変更されたオブジェクト、行または問合せ結果の最新の値を取得するには、アプリケーションからデータベースに問い合せる必要があります。
CQNに適したアプリケーションは、データベースへのネットワーク・ラウンド・トリップを回避するために、変更の頻度の低いオブジェクトに対する問合せの結果セットを中間層にキャッシュするアプリケーションです。これらのアプリケーションでは、CQNを使用して、キャッシュされる問合せを登録できます。このようなアプリケーションは、通知を受信すると、登録済問合せを再実行してキャッシュをリフレッシュできます。
このようなアプリケーションの例としては、Webフォーラムがあります。ユーザーは、コンテンツがデータベースに挿入されても、挿入後すぐにそのコンテンツを参照する必要はないため、このアプリケーションでは情報を中間層にキャッシュし、キャッシュをいつリフレッシュするかをCQNで指示できます。
図15-1に、データがデータベースから提供され、中間層にキャッシュされた後、インターネット経由でアクセスされる一般的な使用例を示します。
中間層のアプリケーションは、キャッシュをデータベースと相対でできるかぎり最新の状態に保ちつつ、データベース・オブジェクトのキャッシュ・コピーに迅速にアクセスする必要があります。キャッシュ・データは、トランザクションでデータが変更されてコミットされると不要になるため、アプリケーションが不正な結果にアクセスする危険性があります。アプリケーションがCQNを使用している場合、データベースは、登録済オブジェクトに変更が発生した時点で変更内容の詳細を使用して通知を公開できます。この通知に応答して、アプリケーションではキャッシュ・データをバックエンド・データベースからフェッチしてリフレッシュできます。
図15-2に、中間層のWebクライアントが通知を受信して処理するプロセスを示します。
図15-2の各手順は次のとおりです(PL/SQLを使用して登録が作成され、アプリケーションによりHR
.EMPLOYEES
に対する問合せの結果セットがキャッシュされている場合)。
開発者は、PL/SQLを使用して、問合せに対するCQN登録を作成します。このプロセスでは、通知を処理するストアドPL/SQLプロシージャを作成した後、PL/SQL CQNインタフェースを使用して問合せに対する登録を作成し、作成したPL/SQLプロシージャを通知ハンドラとして指定します。
データベースでは、データ辞書に登録情報が移入されます。
ユーザーがバックエンド・データベースのHR
.EMPLOYEES
表の行を更新し、その更新をコミットすると、問合せ結果が変更されます。これで、中間層でキャッシュされていたHR
.EMPLOYEES
のデータが無効になります。
データベースにより、内部キューに変更を説明するメッセージが追加されます。
データベースからJOBQ
バックグラウンド・プロセスに、通知メッセージが通知されます。
JOBQ
プロセスにより、クライアント・アプリケーションから指定されたストアド・プロシージャが実行されます。この例では、JOBQ
からサーバー側PL/SQLプロシージャにデータが渡されます。PL/SQL通知ハンドラの実装により、通知の処理方法が決まります。
開発者は、サーバー側PL/SQLプロシージャ内に、登録済オブジェクトに対する変更を中間層のクライアント・アプリケーションに通知するための論理を実装できます。たとえば、HR
.EMPLOYEES
内で変更された行のROWID
をアプリケーションに通知します。
中間層のクライアント・アプリケーションでは、バックエンド・データベースを問い合せて、変更があった行のデータを取得します。
クライアント・アプリケーションにより、キャッシュがこのデータで更新されます。
CQN登録により、1つ以上の問合せのリストが通知タイプおよび通知ハンドラに関連付けられます。
通知タイプは、OCNまたはQRCNです。これらのタイプの詳細は、15.1項および15.2項を参照してください。
CQN登録を作成するには、2つのインタフェースのいずれかを使用します。
PL/SQLインタフェース
PL/SQLインタフェースを使用する場合、通知ハンドラはサーバー側PL/SQLストアド・プロシージャです。15.7項を参照してください。
Oracle Call Interface(OCI)
OCIを使用する場合、通知ハンドラはクライアント側Cコールバック・プロシージャです。15.8項を参照してください。
作成された登録は、データベースに格納されます。Oracle RAC環境では、この登録はすべてのデータベース・インタフェースから参照できます。いずれかのデータベース・インスタンスの問合せ結果がトランザクションによって変更されると、通知が生成されます。
デフォルトでは、登録は、それを作成したアプリケーションによって明示的に登録解除されるまで、または(権限の消失などが原因で)データベースによって暗黙的にパージされるまで存続します。
この項では、PL/SQLを使用したCQN登録の作成について説明します。PL/SQLインタフェースを使用する場合、通知ハンドラはサーバー側PL/SQLストアド・プロシージャです。
内容は次のとおりです。
PL/SQL CQN登録インタフェースは、DBMS_CQ_NOTIFICATION
パッケージを使用して実装されます。登録ブロックを開くには、DBMS_CQ_NOTIFICATION
.NEW_REG_START
ファンクションを使用します。通知タイプや通知ハンドラなどの登録詳細は、CQ_NOTIFICATION$_REG_INFO
オブジェクトの一部として指定し、このオブジェクトは引数としてNEW_REG_START
プロシージャに渡されます。登録ブロックがオープンの間に実行したすべての問合せは、CQNに登録されます。通知タイプとしてQRCNを指定すると、データベースによって各問合せに問合せIDが割り当てられます。この問合せIDは、DBMS_CQ_NOTIFICATION
.CQ_NOTIFICATION_QUERYID
ファンクションを使用して取得できます。登録ブロックをクローズするには、DBMS_CQ_NOTIFICATION
.REG_END
ファンクションを使用します。
段階的な指示は、15.7.6項を参照してください。
参照: DBMS_CQ_NOTIFICATION パッケージの詳細は、『Oracle Database PL/SQLパッケージ・プロシージャおよびタイプ・リファレンス』を参照してください。 |
CQN登録のデフォルトは、表15-1に示すオプションを使用して変更できます。
表15-1 連続問合せ通知登録オプション
オプション | 説明 |
---|---|
通知タイプ |
QRCNを指定します(デフォルトはOCN)。 |
QRCNモード脚注1 |
ベストエフォート・モードを指定します(デフォルトは保証モード)。 |
|
変更された各行の |
操作フィルタ脚注2 |
指定したフィルタ条件を操作タイプが満たした場合のみ、通知を公開します。 |
トランザクション・ラグ脚注2 |
非推奨。かわりに通知グループ化を使用してください。 |
通知グループ化 |
通知のグループ化方法を指定します。 |
信頼可能 |
通知を(デフォルトの共有メモリーではなく)永続データベース・キューに格納します。 |
通知時にパージ |
最初の通知後に登録をパージします。 |
タイムアウト |
指定の時間間隔後に登録をパージします。 |
脚注1 通知タイプがQRCNの場合にのみ適用されます。
脚注2 通知タイプがOCNの場合にのみ適用されます。
内容は次のとおりです。
QRCNモード・オプションは、通知タイプがQRCNの場合にのみ適用されます。通知タイプをQRCNに設定する手順は、15.7.2.1項を参照してください。
QRCNモードには、保証モード(15.2.1項を参照)とベストエフォート・モード(15.2.2項を参照)があります。
デフォルトは保証モードです。ベストエフォート・モードの場合は、CQ_NOTIFICATION$_REG_INFO
オブジェクトのQOSFLAGS
属性にQOS_BEST_EFFORT
を指定します。
ROWID
オプションにより、変更された行それぞれのROWID
疑似列の値(その行のrowid)が通知に含まれるようになります。変更された各行のROWID
オプションを通知に含めるには、CQ_NOTIFICATION$_REG_INFO
オブジェクトのQOSFLAGS
属性にQOS_ROWIDS
を指定します。
注意: ハイブリッド列圧縮(HCC)で圧縮された表の行を更新すると、行のROWID が変更されます。特定のOracleストレージ・システムの機能であるHCCの詳細は、『Oracle Database概要』を参照してください。 |
アプリケーションでは、次の形式の問合せを実行すると、変更された行の内容を通知のROWID
情報から取得できます。
SELECT * FROM table_name_from_notification WHERE ROWID = rowid_from_notification;
ROWID
は、外部文字列形式で公開されます。通常のヒープ表では、ROWID
の長さは18バイトです。索引構成表(IOT)では、ROWID
の長さは主キーのサイズによって異なり、18バイトより長い場合があります。
ROWID
に十分なメモリーがサーバーにない場合、通知はFULL-TABLE-NOTIFICATION
にロールアップされることがあります(これは、通知記述子内の特殊フラグで示されます)。FULL-TABLE-NOTIFICATION
に対して考えられる原因は次のとおりです。
ROWID
によって消費される共有メモリーの合計が動的共有プール・サイズの1%を超えています。
1回のトランザクションで1つの登録済オブジェクト内で変更された行が多すぎます(上限は約80行)。
IOTについて変更された行の論理ROWID
の合計長が長すぎます(上限は約1800バイト)。
通知グループ化オプションNTFN_GROUPING_TYPE
に値DBMS_CQ_NOTIFICATION
.NTFN_GROUPING_TYPE_SUMMARY
が指定されています(15.7.2.6項を参照)。
FULL-TABLE-NOTIFICATION
にはROWID
は含まれないため、これを受信したアプリケーションでは表全体(つまり、すべての行)が変更された可能性があるとみなされます。
操作フィルタ・オプションは、通知タイプがOCNの場合にのみ適用されます。
操作フィルタ・オプションを使用すると、通知を生成する操作のタイプを指定できます。
デフォルトは全操作です。一部の操作でのみ通知が生成されるように指定するには、CQ_NOTIFICATION$_REG_INFO
オブジェクトのOPERATIONS_FILTER
属性を使用します。OPERATIONS_FILTER
属性では、操作のタイプを表す定数を使用してタイプを指定し、この定数は、DBMS_CQ_NOTIFICATION
パッケージで次のように定義されています。
操作 | 定数 |
---|---|
INSERT |
DBMS_CQ_NOTIFICATION.INSERTOP |
UPDATE |
DBMS_CQ_NOTIFICATION.UPDATEOP |
DELETE |
DBMS_CQ_NOTIFICATION.DELETEOP |
ALTEROP |
DBMS_CQ_NOTIFICATION.ALTEROP |
DROPOP |
DBMS_CQ_NOTIFICATION.DROPOP |
UNKNOWNOP |
DBMS_CQ_NOTIFICATION.UNKNOWNOP |
すべて(デフォルト) | DBMS_CQ_NOTIFICATION.ALL_OPERATIONS |
複数の操作を指定するには、ビット単位のOR
を使用します。次に例を示します。
DBMS_CQ_NOTIFICATION.INSERTOP + DBMS_CQ_NOTIFICATION.DELETEOP
QOS_QUERY
は通知タイプとしてQRCNを指定するため、QOSFLAGS
属性にQOS_QUERY
も指定した場合はOPERATIONS_FILTER
は無効です。
参照: DBMS_CQ_NOTIFICATION パッケージの詳細は、『Oracle Database PL/SQLパッケージ・プロシージャおよびタイプ・リファレンス』を参照してください。 |
トランザクション・ラグ・オプションは、通知タイプがOCNの場合にのみ適用されます。
トランザクション・ラグ・オプションでは、クライアント・アプリケーションがデータベースから遅れることのできるトランザクション数を指定します。数が0の場合、登録済オブジェクトを変更するトランザクションごとに通知が送信されます。数が5の場合、登録済オブジェクトを変更するトランザクションのうち5番目ごとに通知が送信されます。データベースでは、クライアントで中間の変更が失われないように、中間の変更をオブジェクトの粒度で追跡して通知に含めます。
0より大きいトランザクション・ラグが役立つのは、アプリケーションでフロー制御の通知を実装している場合のみです。アプリケーションでは、通知がラグに達する程度に十分な頻度で生成されるようにし、無限に遅延されないようにしてください。
TRANSACTION_LAG
を指定すると、QOS_ROWIDS
も指定した場合でも、通知にROWID
は含まれません。
デフォルトでは、通知はその原因となるイベントの直後に生成されます。
通知グループ化オプションは、CQ_NOTIFICATION$_REG_INFO
オブジェクトの属性であり、次の種類があります。
属性 | 説明 |
---|---|
NTFN_GROUPING_CLASS |
通知をグループ化するクラスを指定します。使用可能な値は、DBMS_CQ_NOTIFICATION .NTFN_GROUPING_CLASS_TIME (通知を時間でグループ化)と、デフォルトのゼロ(通知をその原因となるイベントの直後に生成)のみです。 |
NTFN_GROUPING_VALUE |
グループを定義する時間間隔を秒単位で指定します。たとえば、この値が900の場合、同じ15分間隔内に生成された通知がグループ化されます。 |
NTFN_GROUPING_TYPE |
次のいずれかのグループ化のタイプを指定します。
|
NTFN_GROUPING_START_TIME |
通知生成の開始時間を指定します。NULL が指定されると、システム生成の現在の時間がデフォルトになります。 |
NTFN_GROUPING_REPEAT_COUNT |
通知を繰り返す回数を指定します。登録の存続中に通知を永続的に受信するには、DBMS_CQ_NOTIFICATION .NTFN_GROUPING_FOREVER に設定します。登録の存続中に最大でnの通知を受信するには、nに設定します。 |
注意: タイムアウト、権限の消失およびグローバル・イベントによって生成された通知は、指定したグループ化間隔が終了する前に公開される場合があります。この場合、グループ化された保留中の通知が存在すると、間隔が終了する前にその通知も公開されます。 |
デフォルトでは、CQN登録は共有メモリーに格納されます。これをかわりに永続データベース・キューに格納するには(つまり、信頼できる通知を生成するには)、CQ_NOTIFICATION$_REG_INFO
オブジェクトのQOSFLAGS
属性にQOS_RELIABLE
を指定します。
信頼できる通知のメリットは、その生成後にデータベースで障害が発生しても、再起動後にその通知を配信できることです。Oracle RAC環境では、正常なデータベース・インスタンスがこの通知を配信できます。
信頼できる通知のデメリットは、デフォルトの通知よりもCPUおよびI/Oのコストが高いことです。
デフォルトでは、CQN登録は、それを作成したアプリケーションによって明示的に登録解除されるまで、または(権限の消失などが原因で)データベースによって暗黙的にパージされるまで存続します。
登録の最初の通知の生成後にその登録をパージするには、CQ_NOTIFICATION$_REG_INFO
オブジェクトのQOSFLAGS
属性にQOS_DEREG_NFY
を指定します。
n秒後に登録をパージするには、CQ_NOTIFICATION$_REG_INFO
オブジェクトのTIMEOUT
属性にnを指定します。
「通知時にパージ」と「タイムアウト」の2つのオプションは、同時に使用できます。
CQN登録を作成するための前提条件は次のとおりです。
次の権限が必要です。
DBMS_CQ_NOTIFICATION
パッケージのEXECUTE
権限(登録の作成にこのパッケージのサブプログラムを使用)
CHANGE
NOTIFICATION
システム権限
登録する各オブジェクトでのREAD
またはSELECT
権限
登録済の問合せに関連付けられているオブジェクトに対する権限を失うと、通知が生成されます(15.3.3項を参照)。
SYS以外のユーザーとして接続する必要があります。
コミットされていないトランザクションの途中でない必要があります。
dml_locks
init
.ora
パラメータの値がゼロ以外である必要があります(デフォルト値はゼロ以外です)。
(これは、通知を受信するための前提条件でもあります。)
注意: QRCNを使用するには、データベースのCOMPATIBLE 設定が11.0.0以上である必要があります。 |
ストアド・プロシージャやREF
カーソルの一部として実行される問合せなど、ほとんどの問合せをOCNに対して登録できます。
OCNに対して登録不可能な問合せは次のとおりです。
固定表または固定ビューに対する問合せ
ユーザー・ビューに対する問合せ
データベース・リンク(dblink)を含む問合せ
マテリアライズド・ビューでの問合せ
注意: OCN登録ではシノニムを使用できますが、QRCN登録では使用できません。 |
QRCNに対して、保証モードで登録可能な問合せ、ベストエフォート・モードでのみ登録可能な問合せ、およびどちらのモードでも登録不可能な問合せがあります。(モードの詳細は、15.2.1項および15.2.2項を参照してください。)
内容は次のとおりです。
問合せを保証モードでQRCNに対して登録するには、問合せを次のルールに準拠させる必要があります。
参照するすべての列がNUMBER
またはVARCHAR2
データ型である必要があります。
列の式に含まれる算術演算子が次のバイナリ演算子に制限され、そのオペランドが数値データ型の列である必要があります。
+
(加算)
-
(減算、単項マイナスではない)
*
(乗算)
/
(除算)
条件句の比較演算子は次の演算子に制限されます。
<
(より小さい)
<=
(以下)
=
(等しい)
>=
(以上)
>
(より大きい)
<>
または!=
(等しくない)
IS
NULL
IS
NOT
NULL
条件句のBoolean演算子はAND
、OR
およびNOT
に制限されます。
問合せに集計ファンクション(SUM
、COUNT
、AVERAGE
、MIN
、MAX
など)は含められません。
SQL集計ファンクションのリストについては、『Oracle Database SQL言語リファレンス』を参照してください。
保証モードでは、単一表および一部の内部等価結合に対するほとんどの問合せが可能です(次の例を参照)。
SELECT SALARY FROM EMPLOYEES, DEPARTMENTS WHERE EMPLOYEES.DEPARTMENT_ID = DEPARTMENTS.DEPARTMENT_ID AND DEPARTMENTS.LOCATION_ID = 1700;
注意:
|
次のいずれかの条件を満たす問合せは、ベストエフォート・モードでのみQRCNに対して登録可能であり、その簡略バージョンでは通知がオブジェクトの粒度で生成されます。
暗号化が有効にされている列を参照する問合せ
同じ型の項目がSELECT
構文のリストに11以上ある問合せ
次のいずれかを使用した式を含む問合せ
文字列ファンクション(SUBSTR
、LTRIM
、RTRIM
など)
算術ファンクション(TRUNC
、ABS
、SQRT
など)
SQLファンクションのリストについては、『Oracle Database SQL言語リファレンス』を参照してください。
パターン一致条件LIKE
およびREGEXP_LIKE
EXISTS
またはNOT
EXISTS
条件
別の表の列で定義されている条件句を使用した論理和を含む問合せ。次に例を示します。
SELECT EMPLOYEE_ID, DEPARTMENT_ID FROM EMPLOYEES, DEPARTMENTS WHERE EMPLOYEES.EMPLOYEE_ID = 10 OR DEPARTMENTS.DEPARTMENT_ID = 'IT';
ユーザーROWIDアクセスを含む問合せ。次に例を示します。
SELECT DEPARTMENT_ID FROM DEPARTMENTS WHERE ROWID = 'AAANkdAABAAALinAAF';
内部結合以外の結合を含む問合せ
次のいずれかを使用した実行計画を含む問合せ
ビットマップ結合索引、ドメイン索引またはファンクション索引
UNION
ALL
またはCONCATENATION
(問合せ自体の内部、または問合せオプティマイザで選択されたOR
拡張実行計画の結果内で使用。)
ORDER
BY
またはGROUP
BY
(問合せ自体の内部、または問合せオプティマイザで選択された実行計画内のORDER
BY
オプションを使用したSORT
操作の結果内で使用。)
オーバーフロー・セグメントがあり、パーティション化された索引構成表(IOT)
クラスタ・オブジェクト
パラレル実行
次のいずれかを参照する問合せは、保証モードとベストエフォート・モードのどちらでもQRCNに対して登録不可能です。
ビュー
固定表、リモート表または仮想プライベート・データベース(VPD)ポリシーが有効化されている表
DUAL
(SELECT
構文のリスト内)
シノニム
ユーザー定義のPL/SQLサブプログラムのコール
15.7.5.1項に記載されていない演算子
集計ファンクションCOUNT
(他の集計ファンクションは、ベストエフォート・モードでは登録可能ですが、保証モードでは登録不可能です。)
アプリケーション・コンテキスト(次の例を参照)
SELECT SALARY FROM EMPLOYEES WHERE USER = SYS_CONTEXT('USERENV', 'SESSION_USER');
SYSDATE
、SYSTIMESTAMP
またはCURRENT
TIMESTAMP
また、マテリアライズド・ビューを使用して問合せオプティマイザがリライトした問合せはQRCNに対して登録できません。問合せオプティマイザの詳細は、Oracle Database SQLチューニング・ガイドを参照してください。
PL/SQLを使用してCQN登録を作成する手順は、次のとおりです。
通知ハンドラとして機能するストアドPL/SQLプロシージャを作成します。
通知ハンドラの名前、通知タイプ、および登録のその他の属性を指定するCQ_NOTIFICATION$_REG_INFO
オブジェクトを作成します。
クライアント・アプリケーションで、DBMS_CQ_NOTIFICATION
.NEW_REG_START
ファンクションを使用して登録ブロックをオープンします。
登録する問合せを実行します(DML操作またはDDL操作は実行しないでください)。
DBMS_CQ_NOTIFICATION
.REG_END
ファンクションを使用して登録ブロックをクローズします。
内容は次のとおりです。
参照: CQ_NOTIFICATION$_REG_INFO オブジェクトとファンクションNEW_REG_START およびREG_END の詳細は、『Oracle Database PL/SQLパッケージ・プロシージャおよびタイプ・リファレンス』を参照してください。それらすべてはDBMS_CQ_NOTIFICATION パッケージで定義されています。 |
通知ハンドラとして使用するPL/SQLストアド・プロシージャの作成時には、次のシグネチャを含める必要があります。
PROCEDURE schema_name.proc_name(ntfnds IN CQ_NOTIFICATION$_DESCRIPTOR)
このシグネチャで、schema_name
はデータベース・スキーマ名、proc_name
はストアド・プロシージャ名、ntfnds
は通知記述子を示します。
通知記述子はCQ_NOTIFICATION$_DESCRIPTOR
オブジェクトであり、その属性によって変更の詳細(トランザクションID、変更のタイプ、影響を受けた問合せ、変更された表など)が記述されます。
通知記述子ntfnds
はJOBQ
プロセスから通知ハンドラproc_name
に渡され、このハンドラがアプリケーション要件に従って通知を処理します。(これは図15-2の手順6に示します。)
注意: 通知ハンドラは、ジョブ・キュー・プロセス内で実行されます。JOB_QUEUE_PROCESSES 初期化パラメータでは、ジョブの実行用に作成できるプロセスの最大数を指定します。PL/SQL通知を受信するには、JOB_QUEUE_PROCESSES をゼロ以外の値に設定する必要があります。 |
タイプCQ_NOTIFICATION$_REG_INFO
のオブジェクトでは、登録済オブジェクトの変更時にデータベースで実行される通知ハンドラを指定します。SQL*Plusでは、次の文を実行してそのタイプ属性を表示できます。
DESC CQ_NOTIFICATION$_REG_INFO
表15-2に、SYS
.CQ_NOTIFICATION$_REG_INFO
の属性を示します。
表15-2 CQ_NOTIFICATION$_REG_INFOの属性
属性 | 説明 |
---|---|
|
通知の生成時に実行されるPL/SQLプロシージャ(通知ハンドラ)の名前を指定します。この名前は、 |
|
1つ以上のサービスのクオリティ・フラグ( 複数のサービスのクオリティ・フラグを指定するには、ビット単位の |
|
登録のタイムアウト期間を指定します。0(ゼロ)以外の値に設定する場合は、データベースにより登録がパージされるまでの秒数を指定します。
|
|
OCNにのみ適用します(15.1項を参照)。 SQL文の型に基づいてメッセージをフィルタリングします。
ビット単位の |
|
非推奨。フロー制御の通知を実装するには、 OCNにのみ適用します(15.1項を参照)。 クライアントがデータベースから遅れることのできるトランザクション数またはデータベース変更数を指定します。0の場合、クライアントは無効化メッセージを生成直後に受信します。5の場合、登録済オブジェクトを変更するトランザクションのうち5番目ごとに通知が送信されます。データベースでは、中間の変更がオブジェクトの粒度で追跡され、変更が通知とともにバンドルされます。そのため、クライアントから中間の変更が失われることはありません。 トランザクションのコミット時にオブジェクトの変更通知をそれ以上の遅延なしで受信する必要のあるほとんどのアプリケーションでは、トランザクション・ラグ0(ゼロ)を選択するように期待されます。ゼロ以外のトランザクション・ラグが役立つのは、アプリケーションで通知に対するフロー制御を実装している場合のみです。ゼロ以外のトランザクション・ラグを使用する場合は、アプリケーションのワークロードのプロパティを、通知が妥当な頻度で生成されるように設定することをお薦めします。そうしないと、ラグに達するまで通知が無限に遅延する可能性があります。
|
|
通知をグループ化するクラスを指定します。使用可能な値は |
|
グループを定義する時間間隔を秒単位で指定します。たとえば、この値が900の場合、同じ15分間隔内に生成された通知がグループ化されます。 |
|
次のいずれかのグループ化のタイプを指定します。
|
|
通知生成の開始時間を指定します。 |
|
通知を繰り返す回数を指定します。登録の存続中に通知を永続的に受信するには、 |
表15-3のサービスのクオリティ・フラグは、DBMS_CQ_NOTIFICATION
パッケージ内の定数です。このフラグは、CQ_NOTIFICATION$_REG_INFO
のQOS_FLAGS
属性を使用して指定できます(表15-2を参照)。
表15-3 サービスのクオリティ・フラグ
フラグ | 説明 |
---|---|
|
最初の通知後に登録をパージします。 |
|
通知を永続データベース・キューに格納します。 Oracle RAC環境では、データベース・インスタンスで障害が発生すると、正常なデータベース・インスタンスがキューにある通知メッセージを配信できます。 デフォルト: 通知はより効率のよい共有メモリーに格納されます。 |
|
変更された各行の |
|
QRCNに対する問合せを登録します(15.2項を参照)。 QRCNに対する問合せを登録できない場合は、 デフォルト: OCNに対する問合せが登録されます(15.1項を参照) |
|
どの問合せが簡略化されているかを確認するには、静的データ・ディクショナリ・ビュー デフォルト: QRCNに対して問合せが保証モードで登録されます(15.2.1項を参照) |
登録済オブジェクトに変更があるたびに、プロシージャHR
.dcn_callback
を起動する必要があるとします。例15-4では、HR
.dcn_callback
が通知を受け取るように指定するCQ_NOTIFICATION$_REG_INFO
オブジェクトを作成します。このオブジェクトを作成するには、DBMS_CQ_NOTIFICATION
パッケージに対するEXECUTE
権限が必要です。
例15-4 CQ_NOTIFICATION$_REG_INFOオブジェクトの作成
DECLARE v_cn_addr CQ_NOTIFICATION$_REG_INFO; BEGIN -- Create object: v_cn_addr := CQ_NOTIFICATION$_REG_INFO ( 'HR.dcn_callback', -- PL/SQL notification handler DBMS_CQ_NOTIFICATION.QOS_QUERY -- notification type QRCN + DBMS_CQ_NOTIFICATION.QOS_ROWIDS, -- include rowids of changed objects 0, -- registration persists until unregistered 0, -- notify on all operations 0 -- notify immediately ); -- Register queries: ... END; /
問合せの登録済リストに含まれる問合せにより、連続問合せ通知が生成される場合があります。特定の問合せによっていつ通知が生成されるかを確認するには、その問合せのSELECT
構文のリストでDBMS_CQ_NOTIFICATION
.CQ_NOTIFICATION_QUERYID
ファンクションを使用します。次に例を示します。
SELECT EMPLOYEE_ID, SALARY, DBMS_CQ_NOTIFICATION.CQ_NOTIFICATION_QUERYID
FROM EMPLOYEES
WHERE DEPARTMENT_ID = 10;
結果:
EMPLOYEE_ID SALARY CQ_NOTIFICATION_QUERYID ----------- ---------- ----------------------- 200 2800 0 1 row selected.
問合せによって通知が生成される場合、その通知には問合せIDが含まれます。
問合せを既存の登録に追加する手順は、次のとおりです。
既存の登録の登録IDを取得します。
これは、保存済の出力または*_CHANGE_NOTIFICATION_REGS
の問合せから取得できます。
登録IDをパラメータとして使用し、プロシージャDBMS_CQ_NOTIFICATION
.ENABLE_REG
をコールして既存の登録をオープンします。
登録する問合せを実行します(DML操作またはDDL操作は実行しないでください)。
DBMS_CQ_NOTIFICATION
.REG_END
ファンクションを使用して登録をクローズします。
例15-5では、登録IDが21の既存の登録に問合せを追加します。
例15-5 既存の登録への問合せの追加
DECLARE v_cursor SYS_REFCURSOR; BEGIN -- Open existing registration DBMS_CQ_NOTIFICATION.ENABLE_REG(21); OPEN v_cursor FOR -- Run query to be registered SELECT DEPARTMENT_ID FROM HR.DEPARTMENTS; -- register this query CLOSE v_cursor; -- Close registration DBMS_CQ_NOTIFICATION.REG_END; END; /
CQNに最適なパフォーマンスを得るには、次の登録ガイドラインに従ってください。
登録する問合せの数を少なくします。可能であれば、ほとんど変更されないオブジェクトを参照する問合せを登録します。
オブジェクトの揮発性が非常に高いと、多数の通知が送信されることになり、そのオーバーヘッドによってOLTPスループットが低下します。
同じ通知メッセージが複数の受信者宛に複製されないように、特定のオブジェクトについて重複登録の数を最小限に抑えます。
登録を作成できない場合、または登録を作成しても予期した通知を受信していない場合は、次のような原因が考えられます。
JOB_QUEUE_PROCESSES
パラメータがゼロ以外の値に設定されていません。
このため、通知ハンドラを介してPL/SQL通知を受信できません。
登録の作成時にSYSユーザーとして接続しました。
CQN登録を作成するには、SYS以外のユーザーとして接続する必要があります。
登録済オブジェクトを変更した後で、そのトランザクションをコミットしていません。
通知は、トランザクションのコミット時にのみ生成されます。
登録がデータベースに正常に作成されていません。
確認するには、静的データ・ディクショナリ・ビュー*_CHANGE_NOTIFICATION_REGS
を問い合せます。たとえば、次の文により、現行ユーザーに関するすべての登録と登録済オブジェクトが表示されます。
SELECT REGID, TABLE_NAME FROM USER_CHANGE_NOTIFICATION_REGS;
通知ハンドラの実行中に実行時エラーが発生しました。
その場合は、プロシージャの実行を試行したJOBQ
プロセスのトレース・ファイルに記録されます。通常、トレース・ファイルの名前は次の形式になります。
ORACLE_SID_jnumber_PID.trc
たとえば、ORACLE_SIDがdbs1
で、JOBQ
プロセスのプロセスID(PID)が12483の場合、トレース・ファイルの名前は通常、dbs1_j000_12483
.trc
となります。
通知ハンドラに'chnf_callback
'、登録IDに100を指定して登録が作成されているとします。ここで、'chnf_callback
'はデータベースに定義されていないとします。この場合、JOBQ
トレース・ファイルには次の書式のメッセージが含まれます。
**************************************************************************** Runtime error during execution of PL/SQL cbk chnf_callback for reg CHNF100. Error in PLSQL notification of msgid: Queue : Consumer Name : PLSQL function :chnf_callback Exception Occured, Error msg: ORA-00604: error occurred at recursive SQL level 2 ORA-06550: line 1, column 7: PLS-00201: identifier 'CHNF_CALLBACK' must be declared ORA-06550: line 1, column 7: PL/SQL: Statement ignored ****************************************************************************
通知ハンドラの実行中に実行時エラーが発生した場合は、通知を受信していることを確認するために、簡略バージョンの通知ハンドラを作成してから、アプリケーション・ロジックを徐々に追加してください。
非常に単純な通知ハンドラの例は次のとおりです。
REM Create table in HR schema to hold count of notifications received. CREATE TABLE nfcount(cnt NUMBER); INSERT INTO nfcount (cnt) VALUES(0); COMMIT; CREATE OR REPLACE PROCEDURE chnf_callback (ntfnds IN CQ_NOTIFICATION$_DESCRIPTOR) IS BEGIN UPDATE nfcount SET cnt = cnt+1; COMMIT; END; /
トランザクションがコミットされてからエンドユーザーが通知を受信するまでに、タイム・ラグが発生しています。
登録を削除するには、登録IDをパラメータとして使用してプロシージャDBMS_CQ_NOTIFICATION
.DEREGISTER
をコールします。たとえば、次の文により、登録IDが21の登録が解除されます。
DBMS_CQ_NOTIFICATION.DEREGISTER(21);
登録を作成したユーザーまたはSYSユーザーのみが登録を解除できます。
この使用例では、開発者として従業員データ(名前、所在地、電話番号など)を提供するWebアプリケーションを管理しているとします。このアプリケーションはOracle Application Serverで実行され、使用量が多く、バックエンド・データベースにあるHR
.EMPLOYEES
表およびHR
.DEPARTMENTS
表の頻繁な問合せを処理します。この2つの表はあまり変更されないため、アプリケーションでは問合せ結果をキャッシュすることでパフォーマンスを改善できます。キャッシュにより、バックエンド・データベースへのラウンド・トリップ、およびサーバー側での実行待機時間が回避されます。
DBMS_CQ_NOTIFICATION
パッケージを使用すると、HR
.EMPLOYEES
表およびHR
.DEPARTMENTS
表に基づく問合せを登録できます。CQNを構成する手順は、次のとおりです。
15.7.10.1項の説明に従って、通知を処理するサーバー側PL/SQLストアド・プロシージャを作成します。
15.7.10.2項の説明に従って、HR
.EMPLOYEES
表およびHR
.DEPARTMENTS
表へのQRCNに対する問合せを登録します。
これらの手順の完了後は、手順2で登録した問合せの結果に対する変更がコミットされると、手順1で作成した通知ハンドラによってWebアプリケーションに変更が通知され、この直後に、バックエンド・データベースの問合せによりWebアプリケーションでキャッシュがリフレッシュされます。
内容は次のとおりです。
次の説明に従って、通知を処理するサーバー側ストアドPL/SQLプロシージャを作成します。
AS
SYSDBA
でデータベースに接続します。
必要な権限をHR
に付与します。
GRANT EXECUTE ON DBMS_CQ_NOTIFICATION TO HR; GRANT CHANGE NOTIFICATION TO HR;
通知を受信するためにJOB_QUEUE_PROCESSES
パラメータを有効化します。
ALTER SYSTEM SET "JOB_QUEUE_PROCESSES"=4;
SYS以外のユーザー(HR
など)としてデータベースに接続します。
受信した通知イベントのレコードを保持するデータベース表を作成します。
-- Create table to record notification events. DROP TABLE nfevents; CREATE TABLE nfevents ( regid NUMBER, event_type NUMBER ); -- Create table to record notification queries: DROP TABLE nfqueries; CREATE TABLE nfqueries ( qid NUMBER, qop NUMBER ); -- Create table to record changes to registered tables: DROP TABLE nftablechanges; CREATE TABLE nftablechanges ( qid NUMBER, table_name VARCHAR2(100), table_operation NUMBER ); -- Create table to record ROWIDs of changed rows: DROP TABLE nfrowchanges; CREATE TABLE nfrowchanges ( qid NUMBER, table_name VARCHAR2(100), row_id VARCHAR2(2000) );
例15-6に示すように、プロシージャHR
.chnf_callback
を作成します。
例15-6 サーバー側PL/SQL通知ハンドラの作成
CREATE OR REPLACE PROCEDURE chnf_callback ( ntfnds IN CQ_NOTIFICATION$_DESCRIPTOR ) IS regid NUMBER; tbname VARCHAR2(60); event_type NUMBER; numtables NUMBER; operation_type NUMBER; numrows NUMBER; row_id VARCHAR2(2000); numqueries NUMBER; qid NUMBER; qop NUMBER; BEGIN regid := ntfnds.registration_id; event_type := ntfnds.event_type; INSERT INTO nfevents (regid, event_type) VALUES (chnf_callback.regid, chnf_callback.event_type); numqueries :=0; IF (event_type = DBMS_CQ_NOTIFICATION.EVENT_QUERYCHANGE) THEN numqueries := ntfnds.query_desc_array.count; FOR i IN 1..numqueries LOOP -- loop over queries qid := ntfnds.query_desc_array(i).queryid; qop := ntfnds.query_desc_array(i).queryop; INSERT INTO nfqueries (qid, qop) VALUES(chnf_callback.qid, chnf_callback.qop); numtables := 0; numtables := ntfnds.query_desc_array(i).table_desc_array.count; FOR j IN 1..numtables LOOP -- loop over tables tbname := ntfnds.query_desc_array(i).table_desc_array(j).table_name; operation_type := ntfnds.query_desc_array(i).table_desc_array(j).Opflags; INSERT INTO nftablechanges (qid, table_name, table_operation) VALUES ( chnf_callback.qid, tbname, operation_type ); IF (bitand(operation_type, DBMS_CQ_NOTIFICATION.ALL_ROWS) = 0) THEN numrows := ntfnds.query_desc_array(i).table_desc_array(j).numrows; ELSE numrows :=0; -- ROWID info not available END IF; -- Body of loop does not run when numrows is zero. FOR k IN 1..numrows LOOP -- loop over rows Row_id := ntfnds.query_desc_array(i).table_desc_array(j).row_desc_array(k).row_id; INSERT INTO nfrowchanges (qid, table_name, row_id) VALUES (chnf_callback.qid, tbname, chnf_callback.Row_id); END LOOP; -- loop over rows END LOOP; -- loop over tables END LOOP; -- loop over queries END IF; COMMIT; END; /
通知ハンドラの作成後に、例15-7に示すように、HR
.chnf_callback
を通知ハンドラとして指定して、通知を受信する対象となる問合せを登録します。
例15-7 問合せの登録
DECLARE reginfo CQ_NOTIFICATION$_REG_INFO; mgr_id NUMBER; dept_id NUMBER; v_cursor SYS_REFCURSOR; regid NUMBER; BEGIN /* Register two queries for QRNC: */ /* 1. Construct registration information. chnf_callback is name of notification handler. QOS_QUERY specifies result-set-change notifications. */ reginfo := cq_notification$_reg_info ( 'chnf_callback', DBMS_CQ_NOTIFICATION.QOS_QUERY, 0, 0, 0 ); /* 2. Create registration. */ regid := DBMS_CQ_NOTIFICATION.new_reg_start(reginfo); OPEN v_cursor FOR SELECT dbms_cq_notification.CQ_NOTIFICATION_QUERYID, manager_id FROM HR.EMPLOYEES WHERE employee_id = 7902; CLOSE v_cursor; OPEN v_cursor FOR SELECT dbms_cq_notification.CQ_NOTIFICATION_QUERYID, department_id FROM HR.departments WHERE department_name = 'IT'; CLOSE v_cursor; DBMS_CQ_NOTIFICATION.reg_end; END; /
新しく作成した登録を表示:
SELECT queryid, regid, TO_CHAR(querytext) FROM user_cq_notification_queries;
結果は次のようになります。
QUERYID REGID TO_CHAR(QUERYTEXT) ------- ----- ------------------------------------------------ 22 41 SELECT HR.DEPARTMENTS.DEPARTMENT_ID FROM HR.DEPARTMENTS WHERE HR.DEPARTMENTS.DEPARTMENT_NAME = 'IT' 21 41 SELECT HR.EMPLOYEES.MANAGER_ID FROM HR.EMPLOYEES WHERE HR.EMPLOYEES.EMPLOYEE_ID = 7902
次のトランザクションを実行すると、QUERYID
が22の問合せの結果が変更されます。
UPDATE DEPARTMENTS SET DEPARTMENT_NAME = 'FINANCE' WHERE department_name = 'IT';
通知プロシージャchnf_callback
(例15-6で作成したプロシージャ)が実行されます。
通知イベントが記録される表を問合せ:
SELECT * FROM nfevents;
結果は次のようになります。
REGID EVENT_TYPE ----- ---------- 61 7
EVENT_TYPE
7は、EVENT_QUERYCHANGE
(問合せ結果変更)に対応します。
登録済の表への変更が記録される表を問い合せます。
SELECT * FROM nftablechanges;
結果は次のようになります。
REGID TABLE_NAME TABLE_OPERATION ----- -------------- --------------- 42 HR.DEPARTMENTS 4
TABLE_OPERATION
4は、UPDATEOP
(更新操作)に対応します。
変更された行のROWID
が記録される表を問い合せます。
SELECT * FROM nfrowchanges;
結果は次のようになります。
REGID TABLE_NAME ROWID ----- -------------- ------------------ 61 HR.DEPARTMENTS AAANkdAABAAALinAAF
この項では、OCIを使用したCQN登録の作成について説明します。OCIを使用する場合、通知ハンドラはクライアント側Cコールバック・プロシージャです。
トピック
参照: OCIのパブリッシュ・サブスクライブ通知の詳細は、『Oracle Call Interfaceプログラマーズ・ガイド』を参照してください |
連続問合せ(CQ)通知固有のQOS(サービス品質フラグ)を記録するには、サブスクリプション・ハンドルOCI_HTYPE_SUBSCR
に属性OCI_ATTR_SUBSCR_CQ_QOSFLAGS
を設定します。オブジェクトの細分化レベルではなく問合せの細分化レベルで登録するようにリクエストするには、属性OCI_ATTR_SUBSCR_CQ_QOSFLAGS
にOCI_SUBSCR_CQ_QOS_QUERY
フラグ・ビットを設定します。
オプションで擬似列CQ_NOTIFICATION_QUERY_ID
を指定して、登録済問合せの問合せIDを取得できます。この場合、細分化レベルは自動的には問合せレベルに変換されません。戻り時の擬似列の値は、問合せに割り当てられた一意の問合せIDに設定されます。OCIベースの登録の場合は問合せIDの擬似列を省略できますが、その場合は問合せIDが文ハンドルのREAD
属性として送信されます。(この属性はOCI_ATTR_CQ_QUERYID
です)。
通知中に、クライアントが指定したコールバックが起動され、トップレベルの通知記述子が引数として渡されます。
変更済問合せの問合せIDに関する情報は、OCI_DTYPE_CQDES
と呼ばれる特殊な記述子タイプで表されます。問合せ記述子のコレクション(OCIColl
)はトップレベルの通知記述子内に埋め込まれています。各記述子はOCI_DTYPE_CQDES
型です。この問合せ記述子には、次の属性が含まれています。
OCI_ATTR_CQDES_OPERATION
: OCI_EVENT_QUERYCHANGE
またはOCI_EVENT_DEREG
です。
OCI_ATTR_CQDES_QUERYID
: 変更された問合せの問合せIDです。
OCI_ATTR_CQDES_TABLE_CHANGES
: 問合せ結果セットが変更される原因となった表に対するDML操作を記述する表記述子の配列です。各表記述子はOCI_DTYPE_TABLE_CHDES
型です。
コール元セッションには、システム権限CHANGE
NOTIFICATION
、および登録を試行するすべてのオブジェクトに対するSELECT
権限が必要です。登録はデータベースに記録される永続エンティティであり、Oracle RACのすべてのインスタンスに表示されます。登録が問合せ精度の場合、Oracle RACインスタンスで問合せ結果セットが変更され、コミットされる原因となるトランザクションによって通知が生成されます。登録がオブジェクト精度の場合、Oracle RACインスタンスに登録済のオブジェクトを変更するトランザクションによって通知が生成されます。
マテリアライズド・ビューまたは非マテリアライズド・ビューを起動する問合せはサポートされていません。
登録インタフェースは、コールバック、およびAQのネームスペース拡張(DBCHANGE
)を使用して、問合せ対象オブジェクトの変更を通知します。
登録を作成する手順は、次のとおりです。
OCI_EVENTS
およびOCI_OBJECT
モードで環境を作成します。
サブスクリプション・ハンドル属性OCI_ATTR_SUBSCR_NAMESPACE
をネームスペースOCI_SUBSCR_NAMESPACE_DBCHANGE
に設定します。
サブスクリプション・ハンドル属性OCI_ATTR_SUBSCR_CALLBACK
を設定して、問合せハンドルに関連したOCIコールバックを格納します。このコールバックには、次のプロトタイプがあります。
void notification_callback (void *ctx, OCISubscription *subscrhp, void *payload, ub4 paylen, void *desc, ub4 mode);
パラメータの詳細は、『Oracle Call Interfaceプログラマーズ・ガイド』のOCIでの通知コールバックに関する項を参照してください。
必要に応じて、OCI_ATTR_SUBSCR_CTX
を使用してクライアント固有のコンテキストを関連付けます。
OCI_ATTR_SUBSCR_TIMEOUT
属性を設定して、ub4
タイムアウト間隔を秒単位で設定します。設定していない場合は、タイムアウトしません。
OCI_ATTR_SUBSCR_QOSFLAGS
属性を設定し、QOS (サービスのクオリティ)レベルを次の値で設定します。
OCI_SUBSCR_QOS_PURGE_ON_NTFN
フラグでは、最初の通知で登録がパージされます。
OCI_SUBSCR_QOS_RELIABLE
フラグでは、通知が持続されます。Oracle RACの残存インスタンスを使用すると、ノードが失敗した後でも連続問合せ通知メッセージを送信および取得できます。これは、この登録に関連する無効化が、データベースに永続的にキューされるためです。FALSE
の場合、無効化は高速インメモリー・キューにエンキューされます。このオプションは、登録の永続性ではなく、通知の永続性を記述します。デフォルトでは、登録は自動的に持続されます。
OCISubscriptionRegister()
をコールし、DBCHANGE
ネームスペースに新しい登録を作成します。
文ハンドルOCI_HTYPE_STMT
のOCI_ATTR_CHNF_REGHANDLE
属性を設定し、複数の問合せ文をサブスクリプション・ハンドルに関連付けます。問合せが実行されると、登録が完了します。
参照: OCI_ATTR_CHNF_REGHANDLE の詳細は、『Oracle Call Interfaceプログラマーズ・ガイド』を参照してください |
必要に応じて、サブスクリプションの登録を解除します。クライアントはパラメータとしてサブスクリプション・ハンドルを使用して、OCISubscriptionRegister()ファンクションをコールできます。
サブスクリプション・ハンドルへの文ハンドルのバインドが有効なのは、問合せの初回の実行時のみです。アプリケーションが後続の実行時に同じOCI文ハンドルを使用する必要がある場合、アプリケーションは文ハンドルの登録ハンドル属性に再移入する必要があります。文ハンドルをサブスクリプション・ハンドルにバインドできるのは、文が問合せである場合のみです(実行時に判別されます)。実行の一部としてDML文が実行されると、例外が発行されます。
連続問合せ通知のサブスクリプション・ハンドル属性は、汎用属性(すべてのサブスクリプションに対して共通)と、連続問合せ通知に固有のネームスペース特有属性に分けられます。
文ハンドルのWRITE
属性を変更できるのは、登録の作成前のみです。
汎用属性: すべてのサブスクリプションに共通
OCI_ATTR_SUBSCR_NAMESPACE
(WRITE
): サブスクリプション・ハンドルの場合、OCI_SUBSCR_NAMESPACE_DBCHANGE
に、この属性を設定します。
OCI_ATTR_SUBSCR_CALLBACK
(WRITE
): サブスクリプション・ハンドルに関連するコールバックを格納するために、この属性を使用します。通知を受信すると、コールバックが実行されます。
新しい連続問合せ通知メッセージが作成されると、無効化に関する詳細情報が含まれるOCI_DTYPE_CHDES
型の記述子がdesc
に設定されてコールバックがリスナー・スレッド内で呼び出されます。
OCI_ATTR_SUBSCR_QOSFLAGS
: この属性は次の値を含む汎用フラグです。
#define OCI_SUBSCR_QOS_RELIABLE 0x01 /* reliable */ #define OCI_SUBSCR_QOS_PURGE_ON_NTFN 0x10 /* purge on first ntfn */
OCI_SUBSCR_QOS_RELIABLE
: このビットを設定し、通知を持続させます。このため、Oracle RACクラスタの残存インスタンスを使用して、ノードの失敗後も無効化メッセージを送信および取得できます。これは、この登録IDに関連する無効化が、データベースに永続的にキューされるためです。このビットがFALSE
の場合、無効化は高速インメモリー・キューにエンキューされます。このオプションは、登録の永続性ではなく、通知の永続性を記述します。デフォルトでは、登録は自動的に持続されます。
OCI_SUBSCR_QOS_PURGE_ON_NTFN
: このビットを設定し、最初の通知で登録をパージできるようにします。
パラレルのサンプルは、『Oracle Call Interfaceプログラマーズ・ガイド』のOCIのパブリッシュ・サブスクライブ登録機能に関する項を参照してください。
OCI_ATTR_SUBSCR_CQ_QOSFLAGS
: この属性では、連続問合せ通知固有の次のQOSフラグを記述します(モードはWRITE
、データ型はub4
)。
0x1 OCI_SUBSCR_CQ_QOS_QUERY
: このフラグを設定し、問合せレベルの細分性が必須であることを示します。通知は、問合せ結果セットが変更される場合にのみ生成します。デフォルトでは、このQOSレベルにFalseの正数はありません。
0x2 OCI_SUBSCR_CQ_QOS_BEST_EFFORT
: このフラグを設定し、ベスト・エフォート・フィルタリングが許容されることを示します。アプリケーションをキャッシュすることで使用できます。データベースでは、評価のコストに基づく経験則を使用し、完全プルーニングを回避できる場合があります。
OCI_ATTR_SUBSCR_TIMEOUT
: この属性を使用して、秒単位で定義したub4
タイムアウト値を指定します。タイムアウト値が0(ゼロ)または未指定の場合、登録は明示的に解除されるまで有効です。
ネームスペース固有または機能固有属性
次の属性は、連続問合せ通知のネームスペース固有または機能固有です。
OCI_ATTR_CHNF_TABLENAMES
(データ型は(OCIColl *)
): これらは、登録された表名のリストを取得するための属性です。これらの属性は、問合せの実行後にサブスクリプション・ハンドルから使用できます。
OCI_ATTR_CHNF_ROWIDS
: ブール型属性(デフォルトはFALSE
)です。TRUE
の場合、連続問合せ通知メッセージには、操作タイプやROWID
などの行レベルの詳細が含まれます。
OCI_ATTR_CHNF_OPERATIONS
: ub4
フラグを使用して、操作タイプに基づいて通知をフィルタで選択します。登録が問合せレベルの細分性を持つ場合、このオプションは無視されます。用意されているフラグは、次のとおりです。
OCI_OPCODE_ALL
- すべての操作
OCI_OPCODE_INSERT
- 表に対する挿入操作
OCI_OPCODE_UPDATE
- 表に対する更新操作
OCI_OPCODE_DELETE
- 表に対する削除操作
OCI_ATTR_CHNF_CHANGELAG
: クライアントはこのub4
値を使用して、遅延させるトランザクション数を指定できます。クライアントは、このオプションを連続問合せ通知メッセージのスロットル・メカニズムとしても使用できます。このオプションを選択すると、OCI_ATTR_CHNF_ROWIDS
がTRUE
であっても、通知にROWID
レベルの細分性を持つ情報が含まれなくなります。登録が問合せレベルの細分性を持つ場合、このオプションは無視されます。
OCISubscriptionRegister()のコールが起動されると、先行する属性(汎用、名前固有、または機能固有)はすべて、作成済の登録では変更不可になります。これらの属性への変更は、すでに作成されている登録には反映されませんが、新しく作成された、同じ登録ハンドルを使用する登録には反映されます。
参照: 連続問合せ通知の記述子の属性の詳細は、『Oracle Call Interfaceプログラマーズ・ガイド』を参照してください |
NTFNグループ化オプションを使用して、通知の間隔を置くことができます。関連する汎用通知属性は次のとおりです。
OCI_ATTR_SUBSCR_NTFN_GROUPING_VALUE OCI_ATTR_SUBSCR_NTFN_GROUPING_TYPE OCI_ATTR_SUBSCR_NTFN_GROUPING_START_TIME OCI_ATTR_SUBSCR_NTFN_GROUPING_REPEAT_COUNT
参照: データベースへの直接パブリッシュ・サブスクライブ登録のこれらの属性の詳細は、『Oracle Call Interfaceプログラマーズ・ガイド』を参照してください |
OCIStmtExecute()
のコールによる登録後、文ハンドルOCI_HTYPE_STMT
のOCI_ATTR_CQ_QUERYID
属性が登録済問合せの問合せIDを取得します。
参照: OCI_ATTR_CQ_QUERYID の詳細は、『Oracle Call Interfaceプログラマーズ・ガイド』を参照してください |
連続問合せ通知の記述子は、アプリケーションによって指定された通知コールバックのdesc
パラメータに渡されます。次の属性は、連続問合せ通知に固有のものです。連続問合せ通知の記述子のOCI型定数は、OCI_DTYPE_CHDES
です。
通知コールバックは、トップレベルの通知記述子OCI_DTYPE_CHDES
を引数として受け取ります。この記述子には、イベント・タイプがOCI_EVENT_QUERYCHANGE
であるかOCI_EVENT_OBJCHANGE
であるかに基づいて、OCI_DTYPE_CQDES
またはOCI_DTYPE_TABLE_CHDES
記述子のコレクションが含まれます。OCI_EVENT_QUERYCHANGE
型の通知の場合、連続問合せ記述子には表連続問合せ記述子の配列が埋め込まれます。ROWID
レベルの細分性を持つ情報がリクエストされた場合、各OCI_DTYPE_TABLE_CHDES
には変更された各ROWID
に対応する行レベルの連続問合せ記述子(OCI_DTYPE_ROW_CHDES
)の配列が含まれます。
これはトップレベルの連続問合せ通知記述子型です。
OCI_ATTR_CHDES_DBNAME
(oratext *
): データベースの名前(連続問合せ通知のソース)
OCI_ATTR_CHDES_XID
(RAW(8)
): メッセージのメッセージID
OCI_ATTR_CHDES_NFYTYPE
- 通知タイプを示すフラグ。
0x0 OCI_EVENT_NONE
: 連続問合せ通知に関する追加情報なし
0x1 OCI_EVENT_STARTUP
: インスタンスの起動
0x2 OCI_EVENT_SHUTDOWN
: インスタンスの停止
0x3 OCI_EVENT_SHUTDOWN_ANY
: Oracle Real Application Clusters (Oracle RAC)のインスタンスの停止
0x5 OCI_EVENT_DEREG
: 登録解除またはタイムアウト
0x6 OCI_EVENT_OBJCHANGE
: オブジェクト変更通知
0x7 OCI_EVENT_QUERYCHANGE
: 問合せ変更通知
OCI_ATTR_CHDES_TABLE_CHANGES
: 表またはデータ型(OCIColl *)
の操作を記述するコレクション型。この属性は、OCI_ATTR_CHDES_NFTYPE
属性がOCI_EVENT_OBJCHANGE
型の場合のみ表示され、そうでない場合はNULL
になります。コレクションの各要素は、OCI_DTYPE_TABLE_CHDES
型の連続問合せ記述子の表です。
OCI_ATTR_CHDES_QUERIES
: 無効化された問合せを記述するコレクション型。コレクションの各メンバーはOCI_DTYPE_CQDES
型です。この属性は、OCI_ATTR_CHDES_NFTYPE
属性がOCI_EVENT_QUERYCHANGE
型の場合のみ表示され、そうでない場合はNULL
になります。
この通知記述子は、通常はDMLトランザクションまたはDDLトランザクションのコミットに応じて、無効化された問合せを記述します。属性は次のとおりです。
この通知記述子は、登録済問合せに関係する表の変更内容に関する情報を送信します。属性は次のとおりです。
OCI_ATTR_CHDES_TABLE_OPFLAGS
(ub4
)- 表への操作を示すフラグのフィールド。次の各フラグ・フィールドは、属性内の個別のビット位置にあります。
0x1 OCI_OPCODE_ALLROWS
: 表は完全に無効化されています。
0x2 OCI_OPCODE_INSERT
: 表に対する挿入操作。
0x4 OCI_OPCODE_UPDATE
: 表に対する更新操作。
0x8 OCI_OPCODE_DELETE
: 表に対する削除操作。
0x10 OCI_OPCODE_ALTER
: 表は変更されました(スキーマ変更)。これには、行の移行を引き起こしたDDL文と内部操作が含まれます。
0x20 OCI_OPCODE_DROP
: 表は削除されました。
OCI_ATTR_CHDES_TABLE_ROW_CHANGES
- これは、表内の行の変更を示す埋込みコレクションです。コレクションの各要素は、次の属性を持つOCI_DTYPE_ROW_CHDES
型の行連続問合せ記述子です。
参照: 連続問合せ通知の記述子の属性の詳細は、『Oracle Call Interfaceプログラマーズ・ガイド』を参照してください |
例15-8は単純なOCIプログラム、demoquery.c
です。リストにあるコメントを参照してください。コール元セッションには、システム権限CHANGE
NOTIFICATION
、および登録を試行するすべてのオブジェクトに対するSELECT
権限が必要です。
例15-8 連続問合せ通知を示すプログラム・リスト
/* Copyright (c) 2010, Oracle. All rights reserved. */ #ifndef S_ORACLE # include <oratypes.h> #endif /************************************************************************** *This is a DEMO program. To test, compile the file to generate the executable *demoquery. Then demoquery can be invoked from a command prompt. *It will have the following output: Initializing OCI Process Registering query : select last_name, employees.department_id, department_name from employees, departments where employee_id = 200 and employees.department_id = departments.department_id Query Id 23 Waiting for Notifications *Then from another session, log in as HR/HR and perform the following * DML transactions. It will cause two notifications to be generated. update departments set department_name ='Global Admin' where department_id=10; commit; update departments set department_name ='Administration' where department_id=10; commit; *The demoquery program will now show the following output corresponding *to the notifications received. Query 23 is changed Table changed is HR.DEPARTMENTS table_op 4 Row changed is AAAMBoAABAAAKX2AAA row_op 4 Query 23 is changed Table changed is HR.DEPARTMENTS table_op 4 Row changed is AAAMBoAABAAAKX2AAA row_op 4 *The demo program waits for exactly 10 notifications to be received before *logging off and unregistering the subscription. ***************************************************************************/ /*--------------------------------------------------------------------------- PRIVATE TYPES AND CONSTANTS ---------------------------------------------------------------------------*/ /*--------------------------------------------------------------------------- STATIC FUNCTION DECLARATIONS ---------------------------------------------------------------------------*/ #include <stdio.h> #include <stdlib.h> #include <string.h> #include <oci.h> #define MAXSTRLENGTH 1024 #define bit(a,b) ((a)&(b)) static int notifications_processed = 0; static OCISubscription *subhandle1 = (OCISubscription *)0; static OCISubscription *subhandle2 = (OCISubscription *)0; static void checker(/*_ OCIError *errhp, sword status _*/); static void registerQuery(/*_ OCISvcCtx *svchp, OCIError *errhp, OCIStmt *stmthp, OCIEnv *envhp _*/); static void myCallback (/*_ dvoid *ctx, OCISubscription *subscrhp, dvoid *payload, ub4 *payl, dvoid *descriptor, ub4 mode _*/); static int NotificationDriver(/*_ int argc, char *argv[] _*/); static sword status; static boolean logged_on = FALSE; static void processRowChanges(OCIEnv *envhp, OCIError *errhp, OCIStmt *stmthp, OCIColl *row_changes); static void processTableChanges(OCIEnv *envhp, OCIError *errhp, OCIStmt *stmthp, OCIColl *table_changes); static void processQueryChanges(OCIEnv *envhp, OCIError *errhp, OCIStmt *stmthp, OCIColl *query_changes); static int nonractests2(/*_ int argc, char *argv[] _*/); int main(int argc, char **argv) { NotificationDriver(argc, argv); return 0; } int NotificationDriver(argc, argv) int argc; char *argv[]; { OCIEnv *envhp; OCISvcCtx *svchp, *svchp2; OCIError *errhp, *errhp2; OCISession *authp, *authp2; OCIStmt *stmthp, *stmthp2; OCIDuration dur, dur2; int i; dvoid *tmp; OCISession *usrhp; OCIServer *srvhp; printf("Initializing OCI Process\n"); /* Initialize the environment. The environment must be initialized with OCI_EVENTS and OCI_OBJECT to create a continuous query notification registration and receive notifications. */ OCIEnvCreate( (OCIEnv **) &envhp, OCI_EVENTS|OCI_OBJECT, (dvoid *)0, (dvoid * (*)(dvoid *, size_t)) 0, (dvoid * (*)(dvoid *, dvoid *, size_t))0, (void (*)(dvoid *, dvoid *)) 0, (size_t) 0, (dvoid **) 0 ); OCIHandleAlloc( (dvoid *) envhp, (dvoid **) &errhp, OCI_HTYPE_ERROR, (size_t) 0, (dvoid **) 0); /* server contexts */ OCIHandleAlloc((dvoid *) envhp, (dvoid **) &srvhp, OCI_HTYPE_SERVER, (size_t) 0, (dvoid **) 0); OCIHandleAlloc((dvoid *) envhp, (dvoid **) &svchp, OCI_HTYPE_SVCCTX, (size_t) 0, (dvoid **) 0); checker(errhp,OCIServerAttach(srvhp, errhp, (text *) 0, (sb4) 0, (ub4) OCI_DEFAULT)); /* set attribute server context in the service context */ OCIAttrSet( (dvoid *) svchp, (ub4) OCI_HTYPE_SVCCTX, (dvoid *)srvhp, (ub4) 0, (ub4) OCI_ATTR_SERVER, (OCIError *) errhp); /* allocate a user context handle */ OCIHandleAlloc((dvoid *)envhp, (dvoid **)&usrhp, (ub4) OCI_HTYPE_SESSION, (size_t) 0, (dvoid **) 0); OCIAttrSet((dvoid *)usrhp, (ub4)OCI_HTYPE_SESSION, (dvoid *)((text *)"HR"), (ub4)strlen((char *)"HR"), OCI_ATTR_USERNAME, errhp); OCIAttrSet((dvoid *)usrhp, (ub4)OCI_HTYPE_SESSION, (dvoid *)((text *)"HR"), (ub4)strlen((char *)"HR"), OCI_ATTR_PASSWORD, errhp); checker(errhp,OCISessionBegin (svchp, errhp, usrhp, OCI_CRED_RDBMS, OCI_DEFAULT)); /* Allocate a statement handle */ OCIHandleAlloc( (dvoid *) envhp, (dvoid **) &stmthp, (ub4) OCI_HTYPE_STMT, 52, (dvoid **) &tmp); OCIAttrSet((dvoid *)svchp, (ub4)OCI_HTYPE_SVCCTX, (dvoid *)usrhp, (ub4)0, OCI_ATTR_SESSION, errhp); registerQuery(svchp, errhp, stmthp, envhp); printf("Waiting for Notifications\n"); while (notifications_processed !=10) { sleep(1); } printf ("Going to unregister HR\n"); fflush(stdout); /* Unregister HR */ checker(errhp, OCISubscriptionUnRegister(svchp, subhandle1, errhp, OCI_DEFAULT)); checker(errhp, OCISessionEnd(svchp, errhp, usrhp, (ub4) 0)); printf("HR Logged off.\n"); if (subhandle1) OCIHandleFree((dvoid *)subhandle1, OCI_HTYPE_SUBSCRIPTION); if (stmthp) OCIHandleFree((dvoid *)stmthp, OCI_HTYPE_STMT); if (srvhp) OCIHandleFree((dvoid *) srvhp, (ub4) OCI_HTYPE_SERVER); if (svchp) OCIHandleFree((dvoid *) svchp, (ub4) OCI_HTYPE_SVCCTX); if (authp) OCIHandleFree((dvoid *) usrhp, (ub4) OCI_HTYPE_SESSION); if (errhp) OCIHandleFree((dvoid *) errhp, (ub4) OCI_HTYPE_ERROR); if (envhp) OCIHandleFree((dvoid *) envhp, (ub4) OCI_HTYPE_ENV); return 0; } void checker(errhp, status) OCIError *errhp; sword status; { text errbuf[512]; sb4 errcode = 0; int retval = 1; switch (status) { case OCI_SUCCESS: retval = 0; break; case OCI_SUCCESS_WITH_INFO: (void) printf("Error - OCI_SUCCESS_WITH_INFO\n"); break; case OCI_NEED_DATA: (void) printf("Error - OCI_NEED_DATA\n"); break; case OCI_NO_DATA: (void) printf("Error - OCI_NODATA\n"); break; case OCI_ERROR: (void) OCIErrorGet((dvoid *)errhp, (ub4) 1, (text *) NULL, &errcode, errbuf, (ub4) sizeof(errbuf), OCI_HTYPE_ERROR); (void) printf("Error - %.*s\n", 512, errbuf); break; case OCI_INVALID_HANDLE: (void) printf("Error - OCI_INVALID_HANDLE\n"); break; case OCI_STILL_EXECUTING: (void) printf("Error - OCI_STILL_EXECUTE\n"); break; case OCI_CONTINUE: (void) printf("Error - OCI_CONTINUE\n"); break; default: break; } if (retval) { exit(1); } } void processRowChanges(OCIEnv *envhp, OCIError *errhp, OCIStmt *stmthp, OCIColl *row_changes) { dvoid **row_descp; dvoid *row_desc; boolean exist; ub2 i, j; dvoid *elemind = (dvoid *)0; oratext *row_id; ub4 row_op; sb4 num_rows; if (!row_changes) return; checker(errhp, OCICollSize(envhp, errhp, (CONST OCIColl *) row_changes, &num_rows)); for (i=0; i<num_rows; i++) { checker(errhp, OCICollGetElem(envhp, errhp, (OCIColl *) row_changes, i, &exist, &row_descp, &elemind)); row_desc = *row_descp; checker(errhp, OCIAttrGet (row_desc, OCI_DTYPE_ROW_CHDES, (dvoid *)&row_id, NULL, OCI_ATTR_CHDES_ROW_ROWID, errhp)); checker(errhp, OCIAttrGet (row_desc, OCI_DTYPE_ROW_CHDES, (dvoid *)&row_op, NULL, OCI_ATTR_CHDES_ROW_OPFLAGS, errhp)); printf ("Row changed is %s row_op %d\n", row_id, row_op); fflush(stdout); } } void processTableChanges(OCIEnv *envhp, OCIError *errhp, OCIStmt *stmthp, OCIColl *table_changes) { dvoid **table_descp; dvoid *table_desc; dvoid **row_descp; dvoid *row_desc; OCIColl *row_changes = (OCIColl *)0; boolean exist; ub2 i, j; dvoid *elemind = (dvoid *)0; oratext *table_name; ub4 table_op; sb4 num_tables; if (!table_changes) return; checker(errhp, OCICollSize(envhp, errhp, (CONST OCIColl *) table_changes, &num_tables)); for (i=0; i<num_tables; i++) { checker(errhp, OCICollGetElem(envhp, errhp, (OCIColl *) table_changes, i, &exist, &table_descp, &elemind)); table_desc = *table_descp; checker(errhp, OCIAttrGet (table_desc, OCI_DTYPE_TABLE_CHDES, (dvoid *)&table_name, NULL, OCI_ATTR_CHDES_TABLE_NAME, errhp)); checker(errhp, OCIAttrGet (table_desc, OCI_DTYPE_TABLE_CHDES, (dvoid *)&table_op, NULL, OCI_ATTR_CHDES_TABLE_OPFLAGS, errhp)); checker(errhp, OCIAttrGet (table_desc, OCI_DTYPE_TABLE_CHDES, (dvoid *)&row_changes, NULL, OCI_ATTR_CHDES_TABLE_ROW_CHANGES, errhp)); printf ("Table changed is %s table_op %d\n", table_name,table_op); fflush(stdout); if (!bit(table_op, OCI_OPCODE_ALLROWS)) processRowChanges(envhp, errhp, stmthp, row_changes); } } void processQueryChanges(OCIEnv *envhp, OCIError *errhp, OCIStmt *stmthp, OCIColl *query_changes) { sb4 num_queries; ub8 queryid; OCINumber qidnum; ub4 queryop; dvoid *elemind = (dvoid *)0; dvoid *query_desc; dvoid **query_descp; ub2 i; boolean exist; OCIColl *table_changes = (OCIColl *)0; if (!query_changes) return; checker(errhp, OCICollSize(envhp, errhp, (CONST OCIColl *) query_changes, &num_queries)); for (i=0; i < num_queries; i++) { checker(errhp, OCICollGetElem(envhp, errhp, (OCIColl *) query_changes, i, &exist, &query_descp, &elemind)); query_desc = *query_descp; checker(errhp, OCIAttrGet (query_desc, OCI_DTYPE_CQDES, (dvoid *)&queryid, NULL, OCI_ATTR_CQDES_QUERYID, errhp)); checker(errhp, OCIAttrGet (query_desc, OCI_DTYPE_CQDES, (dvoid *)&queryop, NULL, OCI_ATTR_CQDES_OPERATION, errhp)); printf(" Query %d is changed\n", queryid); if (queryop == OCI_EVENT_DEREG) printf("Query Deregistered\n"); checker(errhp, OCIAttrGet (query_desc, OCI_DTYPE_CQDES, (dvoid *)&table_changes, NULL, OCI_ATTR_CQDES_TABLE_CHANGES, errhp)); processTableChanges(envhp, errhp, stmthp, table_changes); } } void myCallback (ctx, subscrhp, payload, payl, descriptor, mode) dvoid *ctx; OCISubscription *subscrhp; dvoid *payload; ub4 *payl; dvoid *descriptor; ub4 mode; { OCIColl *table_changes = (OCIColl *)0; OCIColl *row_changes = (OCIColl *)0; dvoid *change_descriptor = descriptor; ub4 notify_type; ub2 i, j; OCIEnv *envhp; OCIError *errhp; OCIColl *query_changes = (OCIColl *)0; OCIServer *srvhp; OCISvcCtx *svchp; OCISession *usrhp; dvoid *tmp; OCIStmt *stmthp; (void)OCIEnvInit( (OCIEnv **) &envhp, OCI_DEFAULT, (size_t)0, (dvoid **)0 ); (void) OCIHandleAlloc( (dvoid *) envhp, (dvoid **) &errhp, OCI_HTYPE_ERROR, (size_t) 0, (dvoid **) 0); /* server contexts */ (void) OCIHandleAlloc( (dvoid *) envhp, (dvoid **) &srvhp, OCI_HTYPE_SERVER, (size_t) 0, (dvoid **) 0); (void) OCIHandleAlloc( (dvoid *) envhp, (dvoid **) &svchp, OCI_HTYPE_SVCCTX, (size_t) 0, (dvoid **) 0); OCIAttrGet (change_descriptor, OCI_DTYPE_CHDES, (dvoid *) ¬ify_type, NULL, OCI_ATTR_CHDES_NFYTYPE, errhp); fflush(stdout); if (notify_type == OCI_EVENT_SHUTDOWN || notify_type == OCI_EVENT_SHUTDOWN_ANY) { printf("SHUTDOWN NOTIFICATION RECEIVED\n"); fflush(stdout); notifications_processed++; return; } if (notify_type == OCI_EVENT_STARTUP) { printf("STARTUP NOTIFICATION RECEIVED\n"); fflush(stdout); notifications_processed++; return; } notifications_processed++; checker(errhp, OCIServerAttach( srvhp, errhp, (text *) 0, (sb4) 0, (ub4) OCI_DEFAULT)); OCIHandleAlloc( (dvoid *) envhp, (dvoid **) &svchp, (ub4) OCI_HTYPE_SVCCTX, 52, (dvoid **) &tmp); /* set attribute server context in the service context */ OCIAttrSet( (dvoid *) svchp, (ub4) OCI_HTYPE_SVCCTX, (dvoid *)srvhp, (ub4) 0, (ub4) OCI_ATTR_SERVER, (OCIError *) errhp); /* allocate a user context handle */ OCIHandleAlloc((dvoid *)envhp, (dvoid **)&usrhp, (ub4) OCI_HTYPE_SESSION, (size_t) 0, (dvoid **) 0); OCIAttrSet((dvoid *)usrhp, (ub4)OCI_HTYPE_SESSION, (dvoid *)"HR", (ub4)strlen("HR"), OCI_ATTR_USERNAME, errhp); OCIAttrSet((dvoid *)usrhp, (ub4)OCI_HTYPE_SESSION, (dvoid *)"HR", (ub4)strlen("HR"), OCI_ATTR_PASSWORD, errhp); checker(errhp, OCISessionBegin (svchp, errhp, usrhp, OCI_CRED_RDBMS, OCI_DEFAULT)); OCIAttrSet((dvoid *)svchp, (ub4)OCI_HTYPE_SVCCTX, (dvoid *)usrhp, (ub4)0, OCI_ATTR_SESSION, errhp); /* Allocate a statement handle */ OCIHandleAlloc( (dvoid *) envhp, (dvoid **) &stmthp, (ub4) OCI_HTYPE_STMT, 52, (dvoid **) &tmp); if (notify_type == OCI_EVENT_OBJCHANGE) { checker(errhp, OCIAttrGet (change_descriptor, OCI_DTYPE_CHDES, &table_changes, NULL, OCI_ATTR_CHDES_TABLE_CHANGES, errhp)); processTableChanges(envhp, errhp, stmthp, table_changes); } else if (notify_type == OCI_EVENT_QUERYCHANGE) { checker(errhp, OCIAttrGet (change_descriptor, OCI_DTYPE_CHDES, &query_changes, NULL, OCI_ATTR_CHDES_QUERIES, errhp)); processQueryChanges(envhp, errhp, stmthp, query_changes); } checker(errhp, OCISessionEnd(svchp, errhp, usrhp, OCI_DEFAULT)); checker(errhp, OCIServerDetach(srvhp, errhp, OCI_DEFAULT)); if (stmthp) OCIHandleFree((dvoid *)stmthp, OCI_HTYPE_STMT); if (errhp) OCIHandleFree((dvoid *)errhp, OCI_HTYPE_ERROR); if (srvhp) OCIHandleFree((dvoid *)srvhp, OCI_HTYPE_SERVER); if (svchp) OCIHandleFree((dvoid *)svchp, OCI_HTYPE_SVCCTX); if (usrhp) OCIHandleFree((dvoid *)usrhp, OCI_HTYPE_SESSION); if (envhp) OCIHandleFree((dvoid *)envhp, OCI_HTYPE_ENV); } void registerQuery(svchp, errhp, stmthp, envhp) OCISvcCtx *svchp; OCIError *errhp; OCIStmt *stmthp; OCIEnv *envhp; { OCISubscription *subscrhp; ub4 namespace = OCI_SUBSCR_NAMESPACE_DBCHANGE; ub4 timeout = 60; OCIDefine *defnp1 = (OCIDefine *)0; OCIDefine *defnp2 = (OCIDefine *)0; OCIDefine *defnp3 = (OCIDefine *)0; OCIDefine *defnp4 = (OCIDefine *)0; OCIDefine *defnp5 = (OCIDefine *)0; int mgr_id =0; text query_text1[] = "select last_name, employees.department_id, department_name \ from employees,departments where employee_id = 200 and employees.department_id =\ departments.department_id"; ub4 num_prefetch_rows = 0; ub4 num_reg_tables; OCIColl *table_names; ub2 i; boolean rowids = TRUE; ub4 qosflags = OCI_SUBSCR_CQ_QOS_QUERY ; int empno=0; OCINumber qidnum; ub8 qid; char outstr[MAXSTRLENGTH], dname[MAXSTRLENGTH]; int q3out; fflush(stdout); /* allocate subscription handle */ OCIHandleAlloc ((dvoid *) envhp, (dvoid **) &subscrhp, OCI_HTYPE_SUBSCRIPTION, (size_t) 0, (dvoid **) 0); /* set the namespace to DBCHANGE */ checker(errhp, OCIAttrSet (subscrhp, OCI_HTYPE_SUBSCRIPTION, (dvoid *) &namespace, sizeof(ub4), OCI_ATTR_SUBSCR_NAMESPACE, errhp)); /* Associate a notification callback with the subscription */ checker(errhp, OCIAttrSet (subscrhp, OCI_HTYPE_SUBSCRIPTION, (void *)myCallback, 0, OCI_ATTR_SUBSCR_CALLBACK, errhp)); /* Allow extraction of rowid information */ checker(errhp, OCIAttrSet (subscrhp, OCI_HTYPE_SUBSCRIPTION, (dvoid *)&rowids, sizeof(ub4), OCI_ATTR_CHNF_ROWIDS, errhp)); checker(errhp, OCIAttrSet (subscrhp, OCI_HTYPE_SUBSCRIPTION, (dvoid *)&qosflags, sizeof(ub4), OCI_ATTR_SUBSCR_CQ_QOSFLAGS, errhp)); /* Create a new registration in the DBCHANGE namespace */ checker(errhp, OCISubscriptionRegister(svchp, &subscrhp, 1, errhp, OCI_DEFAULT)); /* Multiple queries can now be associated with the subscription */ subhandle1 = subscrhp; printf("Registering query : %s\n", (const signed char *)query_text1); /* Prepare the statement */ checker(errhp, OCIStmtPrepare (stmthp, errhp, query_text1, (ub4)strlen((const signed char *)query_text1), OCI_V7_SYNTAX, OCI_DEFAULT)); checker(errhp, OCIDefineByPos(stmthp, &defnp1, errhp, 1, (dvoid *)outstr, MAXSTRLENGTH * sizeof(char), SQLT_STR, (dvoid *)0, (ub2 *)0, (ub2 *)0, OCI_DEFAULT)); checker(errhp, OCIDefineByPos(stmthp, &defnp2, errhp, 2, (dvoid *)&empno, sizeof(empno), SQLT_INT, (dvoid *)0, (ub2 *)0, (ub2 *)0, OCI_DEFAULT)); checker(errhp, OCIDefineByPos(stmthp, &defnp3, errhp, 3, (dvoid *)&dname, sizeof(dname), SQLT_STR, (dvoid *)0, (ub2 *)0, (ub2 *)0, OCI_DEFAULT)); /* Associate the statement with the subscription handle */ OCIAttrSet (stmthp, OCI_HTYPE_STMT, subscrhp, 0, OCI_ATTR_CHNF_REGHANDLE, errhp); /* Execute the statement, the execution performs object registration */ checker(errhp, OCIStmtExecute (svchp, stmthp, errhp, (ub4) 1, (ub4) 0, (CONST OCISnapshot *) NULL, (OCISnapshot *) NULL , OCI_DEFAULT)); fflush(stdout); OCIAttrGet(stmthp, OCI_HTYPE_STMT, &qid, (ub4 *)0, OCI_ATTR_CQ_QUERYID, errhp); printf("Query Id %d\n", qid); /* commit */ checker(errhp, OCITransCommit(svchp, errhp, (ub4) 0)); } static void cleanup(envhp, svchp, srvhp, errhp, usrhp) OCIEnv *envhp; OCISvcCtx *svchp; OCIServer *srvhp; OCIError *errhp; OCISession *usrhp; { /* detach from the server */ checker(errhp, OCISessionEnd(svchp, errhp, usrhp, OCI_DEFAULT)); checker(errhp, OCIServerDetach(srvhp, errhp, (ub4)OCI_DEFAULT)); if (usrhp) (void) OCIHandleFree((dvoid *) usrhp, (ub4) OCI_HTYPE_SESSION); if (svchp) (void) OCIHandleFree((dvoid *) svchp, (ub4) OCI_HTYPE_SVCCTX); if (srvhp) (void) OCIHandleFree((dvoid *) srvhp, (ub4) OCI_HTYPE_SERVER); if (errhp) (void) OCIHandleFree((dvoid *) errhp, (ub4) OCI_HTYPE_ERROR); if (envhp) (void) OCIHandleFree((dvoid *) envhp, (ub4) OCI_HTYPE_ENV); }
登録のQOSオプションなど、すべての登録に関するトップレベルの情報を確認するには、静的データ・ディクショナリ・ビュー*_CHANGE_NOTIFICATION_REGS
を問い合せます。
たとえば、クライアントの登録IDと通知を受信するオブジェクトのリストを取得できます。HR
の登録IDと表名を表示するには、次の問合せを使用します。
SELECT regid, table_name FROM USER_CHANGE_NOTIFICATION_REGS;
どの問合せがQRCNに対して登録されているかを確認するには、静的データ・ディクショナリ・ビューUSER_CQ_NOTIFICATION_QUERIES
またはDBA_CQ_NOTIFICATION_QUERIES
を問い合せます。これらのビューには、問合せで使用されるバインド値に関する情報が含まれています。これらのビューでは、元の問合せのバインド値が、定数として問合せテキストに含まれます。問合せテキストは、登録済の元の問合せと同等ですが、同一ではない場合があります。
参照: 静的データ・ディクショナリ・ビューUSER_CHANGE_NOTIFICATION_REGS およびDBA_CQ_NOTIFICATION_QUERIES の詳細は、『Oracle Databaseリファレンス』を参照してください。 |
トランザクションのコミット時に、データベースでは登録済オブジェクトがトランザクション中に変更されたかどうかが判別されます。変更されたと判別された場合、登録で指定されている通知ハンドラが実行されます。
内容は次のとおりです。
CQN登録で通知が生成されると、CQ_NOTIFICATION$_DESCRIPTOR
オブジェクトがデータベースから通知ハンドラに渡されます。通知ハンドラでは、データベース変更の詳細をCQ_NOTIFICATION$_DESCRIPTOR
オブジェクトの属性から検出できます。
SQL*Plusでは、SYS
として接続し、次の文を実行するとこれらの属性を表示できます。
DESC CQ_NOTIFICATION$_DESCRIPTOR
表15-4に、CQ_NOTIFICATION$_DESCRIPTOR
の属性の要約を示します。
表15-4 CQ_NOTIFICATION$_DESCRIPTORの属性
属性 | 説明 |
---|---|
|
登録時に戻された登録ID。 |
|
変更を行ったトランザクションのID。 |
|
通知が生成されたデータベースの名前。 |
|
通知をトリガーするデータベース・イベント。たとえば、属性には様々なデータベース・イベントに対応する次の定数を含めることができます。
|
|
変更された表の数。 |
|
このフィールドは、OCN登録の場合にのみ存在します。QRCN登録の場合は
前述以外の場合は |
|
このフィールドは、QRCN登録の場合にのみ存在します。OCN登録の場合は
前述以外の場合は |
CQ_NOTIFICATION$_DESCRIPTOR
タイプには、タイプCQ_NOTIFICATION$_TABLE
の表記述子のVARRAY
を保持する属性TABLE_DESC_ARRAY
が含まれています。
SQL*Plusでは、SYS
として接続し、次の文を実行するとこれらの属性を表示できます。
DESC CQ_NOTIFICATION$_TABLE
表15-5に、CQ_NOTIFICATION$_TABLE
の属性の要約を示します。
表15-5 CQ_NOTIFICATION$_TABLEの属性
属性 | 指定 ... |
---|---|
|
変更があった表に対して実行された操作のタイプ。たとえば、属性には様々なデータベース操作に対応する次の定数を含めることができます。
|
|
変更があった表の名前。 |
|
変更があった行数。 |
|
タイプ |
CQ_NOTIFICATION$_DESCRIPTOR
タイプには、タイプCQ_NOTIFICATION$_QUERY
の結果セット変更記述子のVARRAY
を保持する属性QUERY_DESC_ARRAY
が含まれています。
SQL*Plusでは、SYS
として接続し、次の文を実行するとこれらの属性を表示できます。
DESC CQ_NOTIFICATION$_QUERY
表15-6に、CQ_NOTIFICATION$_QUERY
の属性の要約を示します。
表15-6 CQ_NOTIFICATION$_QUERYの属性
属性 | 指定 ... |
---|---|
|
変更された問合せのID。 |
|
問合せを変更した操作( |
|
タイプ |
登録中にROWID
オプションが指定されると、CQ_NOTIFICATION$_TABLE
タイプにはROW_DESC_ARRAY
属性、つまり、変更された行のROWID
を含むタイプCQ_NOTIFICATION$_ROW
のVARRAY
が設定されます。CQ_NOTIFICATION$_TABLE
オブジェクトのOPFLAGS
フィールドにALL_ROWS
が設定されていた場合、ROWID
情報は使用できません。
表15-7に、CQ_NOTIFICATION$_ROW
の属性の要約を示します。
表15-7 CQ_NOTIFICATION$_ROWの属性
属性 | 指定 ... |
---|---|
|
変更があった表に対して実行された操作のタイプ。表15-5の |
|
変更された行の |