ただし、使用しているデータベースが Web サーバーとは異なるコンピューター上にある場合は、データベースの結果セットをキャッシュすることをお勧めします。ただし、状況に応じて最適なキャッシュ戦略を決定するのは難しい場合があります。たとえば、最新のデータベース結果セットを使用することが重要なアプリケーションの場合、時間トリガーのキャッシュ手法 (有効期限のタイムスタンプに達するたびにキャッシュが再生成されることを前提とするキャッシュ システムで一般的に使用されます) は満足のいく解決策ではない可能性があります。 。この場合、アプリケーションがキャッシュに必要なデータベース データが変更されるたびにアプリケーションに通知し、キャッシュされた期限切れデータとデータベースの一貫性をアプリケーションが保つようにするメカニズムが必要です。この場合、「データベース変更通知」(Oracle Database 10g Release 2の新機能)を使用すると非常に便利です。
データベース変更通知の概要
データベース変更通知機能の使用法は非常に簡単です。通知に対して実行する通知ハンドラ (PL/SQL ストアド プロシージャまたはクライアント OCI コールバック関数) を作成します。 。次に、変更通知を受け取りたいデータベース オブジェクトに対するクエリを登録します。これにより、トランザクションがその中のオブジェクトを変更してコミットするたびに通知ハンドラーが呼び出されます。通常、通知ハンドラーは、クライアント アプリケーションが応答で適切なアクションを実行できるように、変更されたテーブルの名前、行われた変更の種類、およびオプションで変更された行の行 ID をクライアント リスナーに送信します。
データベース変更通知機能がどのように動作するかを理解するために、次の例を考えてみましょう。 PHP アプリケーションが OE.ORDERS テーブルに格納されている注文と OE.ORDER_ITEMS に格納されている品目を注文するとします。発注された注文に関する情報がほとんど変更されないことを考慮すると、アプリケーションで ORDERS テーブルと ORDER_ITEMS テーブルの両方に対するクエリの結果セットをキャッシュすることができます。古いデータへのアクセスを回避するには、データベース変更通知を使用します。これにより、上記の 2 つのテーブルに格納されているデータの変更をアプリケーションに簡単に通知できます。
ORDERS テーブルと ORDER_ITEMS テーブルのクエリを登録する前に、通知を受信して、行われた DML または DDL 変更に応答するには、まず CHANGE NOTIFICATION システム権限と EXECUTE ON DBMS_CHANGENOTIFICATION 権限を OE ユーザーに付与する必要があります。これら 2 つのテーブル。これを行うには、SQL*Plus などの SQL コマンド ライン ツールから次のコマンドを実行します。
CONNECT / AS SYSDBA;
GRANT CHANGE NOTIFICATION TO oe;
GRANT EXECUTE ON DBMS_CHANGE_NOTIFICATION TO oe; PL/SQL 通知を受け取るための命令。または、次の ALTER SYSTEM コマンドを使用することもできます:
ALTER SYSTEM SET "job_queue_processes"=2; その後、OE/OE として接続した後、通知ハンドラーを作成できます。ただし、その前に、通知ハンドラーによって使用されるデータベース オブジェクトを作成する必要があります。たとえば、通知ハンドラーがレジストリの変更を記録するデータベース テーブルを 1 つ以上作成することができます。次の例では、変更が発生した日時、変更されたテーブルの名前、および通知ハンドラーがクライアントに通知メッセージを正常に送信したかどうかを示すメッセージを記録する nfresults テーブルを作成します。
CONNECT oe/oe;
CREATE TABLE nfresults (
operdate DATE,
tblname VARCHAR2(60),
rslt_msg VARCHAR2(100)
);実際には、通知イベントや変更された行の行 ID などの情報を記録するためにさらにテーブルを作成する必要がある場合がありますが、この記事の目的では、nfresults テーブルで十分です。
UTL_HTTP を使用してクライアントに通知を送信します
より保守しやすく柔軟なソリューションを実現するために、1 つ以上の PL/SQL ストアド プロシージャを作成し、通知ハンドラからこれらのストアド プロシージャを呼び出すこともできます。たとえば、クライアントへの通知メッセージを実装するストアド プロシージャを作成するとします。 「リスト 1」は、PL/SQL プロシージャ sendNotification です。このプロセスでは、UTL_HTTPPL パッケージを使用して、クライアント アプリケーションに変更通知を送信します。
リスト 1. UTL_HTTP を使用してクライアントに通知を送信します
コードをコピーします コードは次のとおりです。
CREATE OR REPLACE PROCEDURE sendNotification(url IN VARCHAR2,tblname IN VARCHAR2, order_id IN VARCHAR2) IS
req UTL_HTTP.REQ;
resp UTL_HTTP.RESP; 100);
tbl VARCHAR(60);
BEGIN
tbl:=SUBSTR(tblname, INSTR(tblname, '.', 1, 1) 1, 60); req := UTL_HTTP.BEGIN_REQUEST(url||order_id||'&'||'table='||tbl);
resp := UTL_HTTP.GET_RESPONSE(req);
INSERT INTO nfresults(SYSDATE, tblname、resp.reason_phrase ; 🎜>END
/
「リスト 1」に示すように、sendNotification は、UTL_HTTP.BEGIN_REQUEST 関数によって発行された HTTP リクエストの形式でクライアントに通知メッセージを送信します。この URL には、ORDERS テーブル内の変更された行の order_id が含まれています。次に、UTL_HTTP.GET_RESPONSE を使用して、クライアントから送信された応答情報を取得します。実際、sendNotification はクライアントから返された応答全体を処理する必要はなく、RESP レコードのreason_phrase フィールドに格納されている短いメッセージ (ステータス コードを説明する) を取得するだけです。
通知ハンドラーの作成
これで、上記の sendNotification プロシージャを使用してクライアントに変更通知を送信する通知ハンドラーを作成できます。リスト 2 の PL/SQL プロシージャ order_nf_callback を見てみましょう。
リスト 2. OE.ORDERS テーブルへの変更の通知を処理する通知ハンドラー
コードをコピー コードは次のとおりです。 :
orders_nf_callback (ntfnds IN SYS.CHNF$_DESC) の作成または置換
tblname VARCHAR2(60);
numtables NUMBER; VARCHAR2 (20);
numrows NUMBER;
url VARCHAR2(256) := 'http://webserverhost/phpcache/dropResults.php?order_no='; BEGIN
event_type := ntfnds.event_type;
numtables := ntfnds.numtables;
IF (event_type = DBMS_CHANGE_NOTIFICATION.EVENT_OBJCHANGE) THEN
FOR i IN 1..numtables LOOP
tblname := ntfnds .table_desc_array(i).table_name;
IF (bitand(ntfnds.table_desc_array(i).opflags,
DBMS_CHANGE_NOTIFICATION.ALL_ROWS) = 0) THEN
numrows := ntfnds.table_desc_array(i).numrows ;
ELSE
numrows :=0;
IF (tblname = 'OE.ORDERS') THEN
FOR j IN 1..numrows LOOP
row_id := ntfnds .table_desc_array(i).row_desc_array(j).row_id;
SELECT order_id INTO ord_id FROM 注文 WHERE rowid = row_id;
END LOOP; IF ;
END LOOP;
END IF;
END;
ハンドル プログラムは SYS.CHNF$_DESC オブジェクトをパラメータとして受け取り、そのプロパティを使用して変更の詳細を取得します。この例では、この通知ハンドラーは、登録されたオブジェクトに対する DML または DDL の変更に応じてデータベースによってポストされた通知のみを処理し (つまり、通知タイプが EVENT_OBJCHANGE の場合のみ)、インスタンスの起動やインスタンスなどの他のデータベース イベントに関する情報は無視します。シャットダウン)通知。上記のバージョン以降、ハンドラーは、OE.ORDERS テーブル内の影響を受ける各行に対して発行された変更通知を処理できるようになりました。この記事の後半の「既存の登録へのテーブルの追加」セクションで、ハンドラーに数行のコードを追加して、OE.ORDER_ITEMS テーブル内の変更された行の通知を処理できるようにします。
変更通知の登録を作成する
通知ハンドラーを作成したら、それに対するクエリ登録を作成する必要があります。この例では、登録プロセス中に OE.ORDER テーブルに対してクエリを実行し、orders_nf_callback を通知ハンドラーとして指定する必要があります。通知メッセージで ROWID レベルの粒度を有効にするには、 DBMS_CHANGE_NOTIFICATION パッケージの QOS_ROWIDS オプションを指定する必要もあります。 「リスト 3」は、orders_nf_callback 通知ハンドラのクエリ登録を作成する PL/SQL ブロックです。
リスト 3. 通知ハンドラーのクエリ登録を作成する
regid NUMBER; qosflags NUMBER; REGDS := SYS.CHNF$_REG_INFO; ', qosflags, 0,0,0); regid := DBMS_CHANGE_NOTIFICATION.NEW_REG_START (REGDS); SELECT order_id INTO ord_id FROM order WHERE ROWNUM
END;/
この例では、ORDERS テーブルの登録を作成し、orders_nf_callback を通知ハンドラーとして使用します。ここで、DML または DDL ステートメントを使用して ORDERS テーブルを変更し、トランザクションをコミットすると、orders_nf_callback 関数が自動的に呼び出されます。たとえば、ORDERS テーブルに対して次の UPDATE ステートメントを実行し、トランザクションをコミットします。
UPDATE ORDERS SET order_mode = 'direct' WHERE order_id=2421;
UPDATE ORDERS SET order_mode = 'direct' WHERE order_id=2422 ;
COMMIT;
上記のトランザクションに応じてデータベースが通知を送信したことを確認するには、次のように nfresults テーブルを確認します。 mon-yy hh:mi: ss') operdate,
tblname, rslt_msg FROM nfresults;
結果は次のようになります:
OPERDATE TBLNAME RSLT_MSG
------ ------- ------ ----------- ---------
02-mar-06 04:31:28 OE.ORDERS ではありませんFound
02-mar -06 04:31:29 OE.ORDERS Not Found
上記の結果から、orders_nf_callback は正常に動作していることが明らかですが、クライアント スクリプトが見つかりませんでした。この例では、URL で指定された DropResults.php スクリプトを作成していないため、これは予期せぬことではありません。
既存の登録へのテーブルの追加
前のセクションでは、変更通知サービスを使用して、登録オブジェクト (上の例では ORDERS テーブル) が変更されたときにデータベースに通知させる方法を示しました。ただし、パフォーマンスの観点から、クライアント アプリケーションは、ORDERS テーブル自体ではなく、ORDER_ITEMS テーブルのクエリ結果セットをキャッシュすることを好む場合があります。これは、注文にアクセスするたびに ORDERS テーブルから 1 行だけを取得する必要があるためです。同時に複数の行を ORDER_ITEMS テーブルから取得する必要があります。実際には、注文には数十、場合によっては数百の項目が含まれる場合があります。
ORDERS テーブルのクエリはすでに登録されているため、ORDER_ITEMS テーブルのクエリを登録するために別の登録を作成する必要はありません。代わりに、既存の登録を使用できます。これを行うには、まず既存の登録の ID を取得する必要があります。これを実現するには、次のクエリを実行できます:
SELECT regid, table_name FROM user_change_notification_regs; 結果は次のようになります:
REGID TABLE_NAME
----- ---- -- --------
241 OE.ORDERS
登録 ID を取得した後、次に示すように DBMS_CHANGE_NOTIFICATION.ENABLE_REG 関数を使用して登録に新しいオブジェクトを追加できます。
コードをコピー コードは次のとおりです。
DECLAREord_id NUMBER
BEGIN
DBMS_CHANGE_NOTIFICATION.ENABLE_REG( 241);
SELECT order_id INTO ord_id FROM order_items
DBMS_CHANGE_NOTIFICATION.REG_END;
完了しました。今後、データベースは、ORDERS および ORDER_ITEMS に対する変更に応じて通知を生成し、orders_nf_callback プロシージャを呼び出して通知を処理します。したがって、次の手順では、ORDER_ITEMS テーブルの DML 操作によって生成された通知を処理できるように、orders_nf_callback を編集します。ただし、orders_nf_callback プロシージャを再作成する前に、更新プロセス中に参照される次のテーブル タイプを作成する必要があります。
CREATE TYPE rdesc_tab AS TABLE OF SYS.CHNF$_RDESC; 次に、リストに戻ります。 、次のコード行の後に:
コードは次のとおりです: IF (tblname = 'OE. ORDERS') THEN FOR j IN 1..numrows LOOP
row_id := ntfnds.table_desc_array(i).row_desc_array(j).row_id;SELECT order_id INTO ord_id FROM 注文 WHERE rowid = row_id; 🎜>sendNotification(url, tblname, ord_id) ;
END LOOP;
次のコードを挿入します:
code
IF (tblname = 'OE.ORDER_ITEMS') THEN FOR rec IN (SELECT DISTINCT(o.order_id) o_id FROM TABLE(CAST(ntfnds.table_desc_array(i).row_desc_array AS rdesc_tab)) t, orders o, order_items d WHERE t.row_id = d.rowid AND d.order_id=o.order_id)
LOOPsendNotification(url、tblname、rec.o_id);
END LOOP
orders_nf_callback を再作成した後、正しく動作するかどうかをテストする必要があります。これを行うには、ORDER_ITEMS テーブルに対して次の UPDATE ステートメントを実行し、トランザクションをコミットします。
UPDATE ORDER_ITEMS SETquantity = 160 WHERE order_id=2421 AND line_item_id=1; WHERE order_id= 2421 AND line_item_id=2;
COMMIT;
次に、次のように nfresults テーブルを確認します。 operdate,
rslt_msg FROM nfresults WHERE tblname = 'OE.ORDER_ITEMS'; 出力は次のようになります:
OPERDATE RSLT_MSG
-------------- - --------------
03-mar-06 12:32:27 Not Found
結局のところ、なぜ 1 行だけが nfresults テーブルに挿入されたのか疑問に思われるかもしれません。 、ORDER_ITEMS テーブル内の 2 つの行を更新しました。実際、更新された 2 つの行は同じ order_id を持っています。つまり、同じ注文に属しています。ここでは、クライアント アプリケーションが 1 つのステートメントを使用して注文のすべての明細を選択すると想定しているため、注文のどの明細が変更されたかを正確に知る必要はありません。代わりに、クライアントは、少なくとも 1 つの明細項目が変更、削除、または挿入された注文 ID を知る必要があります。
クライアントの構築
ORDERS テーブルと ORDER_ITEMS テーブルの登録を作成したので、これらのテーブルに格納されている注文とその品目にアクセスするクライアント アプリケーションによって変更通知がどのように使用されるかを見てみましょう。これを行うには、上記のテーブルに対するクエリの結果をキャッシュし、これらのテーブルへの変更に関する通知 (データベース サーバーから受信する) に応じて適切なアクションを実行する PHP アプリケーションを構築できます。簡単な方法は、キャッシュ データを最新の状態に保つための信頼できるメカニズムを提供する PEAR::Cache_Lite パッケージを使用することです。特に、Cache_Lite_Function クラス (PEAR::Cache_Lite パッケージの一部) を使用すると、関数呼び出しをキャッシュできます。
たとえば、データベース接続を確立し、データベースに対して選択ステートメントを実行し、検索結果を取得し、最後に結果を配列として返すというタスクを実行する関数を作成できます。その後、Cache_Lite_Function インスタンスの call メソッドを通じて関数から返された結果配列をキャッシュし、バックエンド データベースではなくローカル キャッシュから読み取れるようにすることで、アプリケーションのパフォーマンスを大幅に向上させることができます。その後、キャッシュされたデータへの変更が通知されたら、Cache_Lite_Function インスタンスのdrop メソッドを使用して、期限切れのデータをキャッシュから削除します。
この記事の例を振り返ると、アプリケーションがデータベースと対話するために 2 つの関数を作成するとよいでしょう。最初の関数は ORDERS テーブルにクエリを実行し、指定された ID を持つ注文を返します。もう 1 つの関数は、 ORDER_ITEMS テーブルをクエリして、その注文の品目を返します。リスト 4 は、getOrderFields 関数を含む getOrderFields.php スクリプトを示しています。この関数は注文 ID を受け取り、取得した注文のフィールドの一部を含む連想配列を返します。
リスト 4. 指定された順序のフィールドを取得します
コードをコピーします
コードは次のとおりです: < ;?php
//File:getOrderFields.phprequire_once 'connect.php';
function getOrderFields($order_no) {
if (!$rsConnection = GetConnection( )){
return false;
}
$strSQL = "SELECT TO_CHAR(ORDER_DATE) ORDER_DATE、CUSTOMER_ID、
ORDER_TOTAL FROM ORDERS WHERE order_id =:order_no"; $rsConnection,$strSQL) ;
oci_bind_by_name($rsStatement, ":order_no", $order_no, 12);
if (!oci_execute($rsStatement)) {
$err = oci_error(); 🎜>print $err[ 'message'];
trigger_error('Query failed:' . $err['message']);
return false;
$results = oci_fetch_assoc($) rsStatement);
return $results;
}
?>
「リスト 5」は getOrderItems.php スクリプトです。スクリプトには getOrderItems 関数が含まれており、この関数は注文 ID を受け取り、注文の品目を表す行を含む 2 次元配列を返します。
リスト 5. 指定された順序の品目を取得します
//File:getOrderItems.php require_once 'connect.php'; function getOrderItems($order_no) {
if (!$rsConnection = GetConnection ()){return false;
}
$strSQL = "SELECT * FROM ORDER_ITEMS WHERE
order_id =:order_no ORDER BY line_item_id"; strSQL);
oci_bind_by_name($rsStatement, ":order_no", $order_no, 12);
if (!oci_execute($rsStatement)) {
$err = oci_error(); 'クエリが失敗しました:' . $err['message']);
return false;
$nrows = oci_fetch_all($rsStatement, $results);結果);
}
?>
上記の 2 つの関数には connect.php スクリプトが必要であることに注意してください。このスクリプトには、データベース接続を返す GetConnection 関数が含まれている必要があります。リスト 6 は connect.php スクリプトです。
リスト 6. データベース接続の取得
コードをコピー コードは次のとおりです。
//File:connect.php
function GetConnection() {
$dbHost = "dbserverhost"
$dbHostPort="1521"; ;
$dbServiceName = "orclR2";
$pswd = "oe";
$dbConnStr = "(説明=(プロトコル=TCP) HOST=". $dbHost.")
(PORT=".$dbHostPort."))(CONNECT_DATA=(SERVICE_NAME=".$dbServiceName.")))";
if(!$dbConn = oci_connect ($usr, $pswd,$dbConnStr)) {
$err = oci_error();
trigger_error('接続に失敗しました ' .$err['message']);
return false; >}
return $dbConn;
}
?>
データベースとの通信に必要な関数をすべて作成したので、次はその方法を見てみましょう。 Cache_Lite_Function クラスが機能します。リスト 7 は、Cache_Lite_Function クラスを使用して上記の関数の結果をキャッシュする testCache.php スクリプトです。
コードのコピー コードは次のとおりです。
//File:testCache.phpDATE: | ".$orderfields['ORDER_DATE']." | < ;/tr>gt;" ;
CUST_ID: | gt;".$orderfields['CUSTOMER_ID']." | gt;
TOTAL: | ".$orderfields['ORDER_TOTAL']." |
$key | n";"; /td>"; print " | ".$orderitems['LINE_ITEM_ID'][$i]." | ";".$orderitems[ 'PRODUCT_ID'][ $i]." | ";".$orderitems['UNIT_PRICE'][$i]." | "; >print "< td>".$orderitems['QUANTITY'][$i]."";
---|
コードをコピー コードは次のとおりです:
//File:dropResults.php
require_once 'Cache/Lite/Function.php';
$options = array(
'cacheDir' => '/ tmp/'
);
$cache = new Cache_Lite_Function($options)
if (isset($_GET['order_no'])&& isset($_GET['table']); {
if($_GET['table']=='ORDER_ITEMS'){
$cache->drop('getOrderItems', $_GET['order_no']);
if ( $_GET['table']=='ORDERS'){
$cache->drop('getOrderFields', $_GET['order_no']);
}
}
?>
dropResult.php スクリプトを作成した後、通知ハンドラー (リスト 2 を参照) で指定された URL が正しいことを確認してください。次に、SQL*Plus または同様のツールで OE/OE として接続し、このセクションの前半で testCache.php スクリプトを使用してアクセスしたのと同じ順序 (ここでは ID 2408 の順序) に影響を与える UPDATE ステートメントを実行します。 🎜>UPDATE ORDERS SET order_mode = 'direct' WHERE order_id=2408;
UPDATE ORDER_ITEMS SET 数量 = 3 WHERE order_id=2408 AND line_item_id=1;
UPDATE ORDER_ITEMS SET 数量 = 1 WHERE order_id=2408 AND line_item_id =2 ;
COMMIT;
上記の更新に応じて、この記事で説明した通知ハンドラーは、次の URL を使用して、dropResults.php スクリプトを 2 回実行します。 phpcache/dropResults.php?order_no=2408&table=ORDERS
http://webserverhost/phpcache/dropresults.php?order_no=2408&table=ORDER_ITEMS
「リスト 8」から、dropResult.php スクリプトが以下であることが明確にわかります。データベース サーバーから変更通知を受信した後、キャッシュはフラッシュされませんでした。期限切れのデータを含むキャッシュ ファイルを削除するだけです。ここでキャッシュ ディレクトリを確認すると、order_no=2408 を指定して testCache.php スクリプトを実行したときに作成されたキャッシュ ファイルが消えていることがわかります。これが本質的に意味するのは、次回 testCache.php が注文 ID 2408 に関連するデータをリクエストするときに、ローカル キャッシュではなくバックエンド データベースからそのデータを取得するということです。
このメソッドは、アプリケーションが要求した結果セットがアプリケーションで使用される前に変更される可能性がある状況で役立つ場合があります。この記事の例では、これは、testCache.php がその注文にアクセスする前に、特定の注文に関連するデータが複数回変更される可能性があることを意味します。このように、アプリケーションはデータベース サーバーから変更通知を受信した直後にキャッシュをフラッシュすることで、多くの不必要な作業を実行します。
ただし、dropResult.php スクリプトで変更通知を受け取った直後にキャッシュをフラッシュしたい場合は、drop メソッドを呼び出した後に Cache_Lite_Function インスタンスの call メソッドを呼び出し、両方の呼び出しに同じパラメーターを指定できます。 。この場合、dropResults.php が getOrderFields 関数と getOrderItems 関数を呼び出してキャッシュを更新できるように、getOrderFields.php スクリプトと getOrderItems.php スクリプトも必ず含める必要があります。 「リスト 9」は、変更されたdropResult.phpスクリプトです。
リスト 9. 変更通知を受信した直後にキャッシュを更新する
コードをコピー
コードは次のとおりです: //File:dropResults.php
require_once 'Cache/Lite/Function.php';require_once 'getOrderItems.php';
require_once 'getOrderFields.php ' ;
$options = array(
'cacheDir' => '/tmp/',
'lifeTime' => 86400
);オプション );
if (isset($_GET['order_no'])&& isset($_GET['table'])) {
if($_GET['table']=='ORDER_ITEMS'){
$cache->drop('getOrderItems', $_GET['order_no']);
$cache->call('getOrderItems', $_GET['order_no']);
if ($_GET['table']=='ORDERS'){
$cache->drop('getOrderFields', $_GET['order_no']); call ('getOrderFields', $_GET['order_no']);
}
}
?>
ORDERS テーブルと ORDER_ITEMS テーブルに格納されたデータがほとんど変更されない場合アプリケーションが頻繁にアクセスする場合、上記の方法が役立つ可能性があります。
概要