眾所周知緩存數據庫查詢的結果可以顯著縮短腳本執行時間並最大限度地減少數據庫服務器上的負載如果要處理的數據基本上是靜態的則該技術將非常有效這是因為對遠程數據庫的許多數據請求最終可以從本地緩存得到滿足從而不必連接到數據庫執行查詢以及獲取結果
但當您使用的數據庫與 Web 服務器位於不同的計算機上時緩存數據庫結果集通常是一個不錯的方法不過根據您的情況確定最佳的緩存策略卻是一個難題例如對於使用最新數據庫結果集比較重要的應用程序而言時間觸發的緩存方法(緩存系統常用的方法它假設每次到達失效時間戳記時就重新生成緩存)可能並不是一個令人滿意的解決方案這種情況下您需要采用一種機制每當應用程序需要緩存的數據庫數據發生更改時該機制將通知該應用程序以便該應用程序將緩存的過期數據與數據庫保持一致這種情況下使用數據庫更改通知(一個新的 Oracle 數據庫 g 第 版特性)將非常方便
數據庫更改通知入門
數據庫更改通知特性的用法非常簡單創建一個針對通知執行的通知處理程序 – 一個 PL/SQL 存儲過程或客戶端 OCI 回調函數然後針對要接收其更改通知的數據庫對象注冊一個查詢以便每當事務更改其中的任何對象並提交時調用通知處理程序通常情況下通知處理程序將被修改的表的名稱所做更改的類型以及所更改行的行 ID(可選)發送給客戶端監聽程序以便客戶端應用程序可以在響應中執行相應的處理
為了了解數據庫更改通知特性的作用方式請考慮以下示例假設您的 PHP 應用程序訪問 OEORDERS 表中存儲的訂單以及 OEORDER_ITEMS 中存儲的訂單項鑒於很少更改已下訂單的信息您可能希望應用程序同時緩存針對 ORDERS 和 ORDER_ITEMS 表的查詢結果集要避免訪問過期數據您可以使用數據庫更改通知它可讓您的應用程序方便地獲知以上兩個表中所存儲數據的更改
您必須先將 CHANGE NOTIFICATION 系統權限以及 EXECUTE ON DBMS_CHANGENOTIFICATION 權限授予 OE 用戶才能注冊對 ORDERS 和 ORDER_ITEMS 表的查詢以便接收通知和響應對這兩個表所做的 DML 或 DDL 更改為此可以從 SQL 命令行工具(如 SQL*Plus)中執行下列命令
CONNECT / AS SYSDBA;
GRANT CHANGE NOTIFICATION TO oe;
GRANT EXECUTE ON DBMS_CHANGE_NOTIFICATION TO oe;
確保將 initora 參數 job_queue_processes 設置為非零值以便接收 PL/SQL 通知或者您也可以使用下面的 ALTER SYSTEM 命令
ALTER SYSTEM SET job_queue_processes=;
然後在以 OE/OE 連接後您可以創建一個通知處理程序但首先您必須創建將由通知處理程序使用的數據庫對象例如您可能需要創建一個或多個數據庫表以便通知處理程序將注冊表的更改記錄到其中在以下示例中您將創建 nfresults 表來記錄以下信息更改發生的日期和時間被修改的表的名稱以及一個消息(說明通知處理程序是否成功地將通知消息發送給客戶端)
CONNECT oe/oe;
CREATE TABLE nfresults (
operdate DATE
tblname VARCHAR()
rslt_msg VARCHAR()
);
在實際情況中您可能需要創建更多表來記錄通知事件以及所更改行的行 ID 等信息但就本文而言nfresults 表完全可以滿足需要
使用 UTL_HTTP 向客戶端發送通知
您可能還要創建一個或多個 PL/SQL 存儲過程並從通知處理程序中調用這些存儲過程從而實現一個更具可維護性和靈活性的解決方案例如您可能要創建一個實現將通知消息發送給客戶端的存儲過程清單 是 PL/SQL 過程 sendNotification該過程使用 UTL_HTTPPL 程序包向客戶端應用程序發送更改通知
清單 使用 UTL_HTTP 向客戶端發送通知
CREATE OR REPLACE PROCEDURE sendNotification(url IN VARCHAR
tblname IN VARCHAR order_id IN VARCHAR) IS
req UTL_HTTPREQ;
resp UTL_HTTPRESP;
err_msg VARCHAR();
tbl VARCHAR();
BEGIN
tbl:=SUBSTR(tblname INSTR(tblname )+ );
BEGIN
req := UTL_HTTPBEGIN_REQUEST(url||order_id||&||table=||tbl);
resp := UTL_HTTPGET_RESPONSE(req);
INSERT INTO nfresults VALUES(SYSDATE tblname respreason_phrase);
UTL_HTTPEND_RESPONSE(resp);
EXCEPTION WHEN OTHERS THEN
err_msg := SUBSTR(SQLERRM );
INSERT INTO nfresults VALUES(SYSDATE tblname err_msg);
END;
COMMIT;
END;
/
如清單 所示sendNotification 以 UTL_HTTPBEGIN_REQUEST 函數發出的 HTTP 請求的形式向客戶端發送通知消息此 URL 包含 ORDERS 表中已更改行的 order_id然後它使用 UTL_HTTPGET_RESPONSE 獲取客戶端發出的響應信息實際上sendNotification 並不需要處理客戶端返回的整個響應而是只獲取一個在 RESP 記錄的 reason_phrase 字段中存儲的簡短消息(描述狀態代碼)
創建通知處理程序
現在您可以創建一個通知處理程序它將借助於上面介紹的 sendNotification 過程向客戶端發送更改通知來看一看清單 中的 PL/SQL 過程 orders_nf_callback
清單 處理對 OEORDERS 表所做更改的通知的通知處理程序
CREATE OR REPLACE PROCEDURE orders_nf_callback (ntfnds IN SYSCHNF$_DESC) IS
tblname VARCHAR();
numtables NUMBER;
event_type NUMBER;
row_id VARCHAR();
numrows NUMBER;
ord_id VARCHAR();
url VARCHAR() := ;
BEGIN
event_type := ntfndsevent_type;
numtables := ntfndsnumtables;
IF (event_type = DBMS_CHANGE_NOTIFICATIONEVENT_OBJCHANGE) THEN
FOR i IN numtables LOOP
tblname := ntfndstable_desc_array(i)table_name;
IF (bitand(ntfndstable_desc_array(i)opflags
DBMS_CHANGE_NOTIFICATIONALL_ROWS) = ) THEN
numrows := ntfndstable_desc_array(i)numrows;
ELSE
numrows :=;
END IF;
IF (tblname = OEORDERS) THEN
FOR j IN numrows LOOP
row_id := ntfndstable_desc_array(i)row_desc_array(j)row_id;
SELECT order_id INTO ord_id FROM orders WHERE rowid = row_id;
sendNotification(url tblname ord_id);
END LOOP;
END IF;
END LOOP;
END IF;
COMMIT;
END;
/
如清單 所示此通知處理程序將 SYSCHNF$_DESC 對象用作參數然後使用它的屬性獲取該更改的詳細信息在該示例中此通知處理程序將只處理數據庫為響應對注冊對象進行的 DML 或 DDL 更改(也就是說僅當通知類型為 EVENT_OBJCHANGE 時)而發布的通知並忽略有關其他數據庫事件(如實例啟動或實例關閉)的通知從以上版本開始處理程序可以處理針對 OEORDERS 表中每個受影響的行發出的更改通知在本文後面的將表添加到現有注冊部分中您將向處理程序中添加幾行代碼以便它可以處理針對 OEORDER_ITEMS 表中被修改的行發出的通知
為更改通知創建注冊
創建通知處理程序後必須為其創建一個查詢注冊對於本示例而言您必須在注冊過程中對 OEORDER 表執行查詢並將 orders_nf_callback 指定為通知處理程序您還需要在 DBMS_CHANGE_NOTIFICATION 程序包中指定 QOS_ROWIDS 選項以便在通知消息中啟用 ROWID 級別的粒度清單 是一個 PL/SQL 塊它為 orders_nf_callback 通知處理程序創建查詢注冊
清單 為通知處理程序創建查詢注冊
DECLARE
REGDS SYSCHNF$_REG_INFO;
regid NUMBER;
ord_id NUMBER;
qosflags NUMBER;
BEGIN
qosflags := DBMS_CHANGE_NOTIFICATIONQOS_RELIABLE +
DBMS_CHANGE_NOTIFICATIONQOS_ROWIDS;
REGDS := SYSCHNF$_REG_INFO (orders_nf_callback qosflags );
regid := DBMS_CHANGE_NOTIFICATIONNEW_REG_START (REGDS);
SELECT order_id INTO ord_id FROM orders WHERE ROWNUM<;
DBMS_CHANGE_NOTIFICATIONREG_END;
END;
/
本示例針對 ORDERS 表創建了一個注冊並將 orders_nf_callback 用作通知處理程序現在如果您使用 DML 或 DDL 語句修改 ORDERS 表並提交事務則將自動調用 orders_nf_callback 函數例如您可能針對 ORDERS 表執行下列 UPDATE 語句並提交該事務
UPDATE ORDERS SET order_mode = direct WHERE order_id=;
UPDATE ORDERS SET order_mode = direct WHERE order_id=;
COMMIT;
要確保數據庫發布了通知來響應以上事務您可以檢查 nfresults 表
SELECT TO_CHAR(operdate ddmonyy hh:mi:ss) operdate
tblname rslt_msg FROM nfresults;
結果應如下所示
OPERDATE TBLNAME RSLT_MSG
mar :: OEORDERS Not Found
mar :: OEORDERS Not Found
從以上結果中可以清楚地看到orders_nf_callback 已經正常工作但未找到客戶端腳本在該示例中出現這種情況並不意外這是因為您並未創建 URL 中指定的 dropResultsphp 腳本有關 dropResultsphp 腳本的說明請參閱本文後面的構建客戶端 部分
將表添加到現有注冊
前一部分介紹了如何使用更改通知服務使數據庫在注冊對象(在以上示例中為 ORDERS 表)發生更改時發出通知但從性能角度而言客戶端應用程序可能更希望緩存 ORDER_ITEMS 表而非 ORDERS 表本身的查詢結果集這是因為它在每次訪問訂單時不得不從 ORDERS 表中只檢索一行但同時必須從 ORDER_ITEMS 表中檢索多個行在實際情況中訂單可能包含數十個甚至數百個訂單項
由於您已經對 ORDERS 表注冊了查詢因此不必再創建一個注冊來注冊對 ORDER_ITEMS 表的查詢了相反您可以使用現有注冊為此您首先需要檢索現有注冊的 ID可以執行以下查詢來完成此工作
SELECT regid table_name FROM user_change_notification_regs;
結果可能如下所示
REGID TABLE_NAME
OEORDERS
獲取注冊 ID 後可以使用 DBMS_CHANGE_NOTIFICATIONENABLE_REG 函數將一個新對象添加到該注冊如下所示
DECLARE
ord_id NUMBER;
BEGIN
DBMS_CHANGE_NOTIFICATIONENABLE_REG();
SELECT order_id INTO ord_id FROM order_items WHERE ROWNUM < ;
DBMS_CHANGE_NOTIFICATIONREG_END;
END;
/
完成了!從現在開始數據庫將生成一個通知來響應對 ORDERS 和 ORDER_ITEMS 所做的任何更改並調用 orders_nf_callback 過程來處理通知因此下一步就是編輯 orders_nf_callback以便它可以處理因對 ORDER_ITEMS 表執行 DML 操作而生成的通知但在重新創建 orders_nf_callback 過程之前您需要創建以下將在更新過程中引用的表類型
CREATE TYPE rdesc_tab AS TABLE OF SYSCHNF$_RDESC;
然後返回清單 在以下代碼行之後
IF (tblname = OEORDERS) THEN
FOR j IN numrows LOOP
row_id := ntfndstable_desc_array(i)row_desc_array(j)row_id;
SELECT order_id INTO ord_id FROM orders WHERE rowid = row_id;
sendNotification(url tblname ord_id);
END LOOP;
END IF;
插入以下代碼
IF (tblname = OEORDER_ITEMS) THEN
FOR rec IN (SELECT DISTINCT(oorder_id) o_id FROM
TABLE(CAST(ntfndstable_desc_array(i)row_desc_array AS rdesc_tab)) t
orders o order_items d WHERE trow_id = drowid AND dorder_id=oorder_id)
LOOP
sendNotification(url tblname reco_id);
END LOOP;
END IF;
重新創建 orders_nf_callback 後您需要測試它能否正常工作為此您可以針對 ORDER_ITEMS 表執行下列 UPDATE 語句並提交該事務
UPDATE ORDER_ITEMS SET quantity = WHERE order_id= AND line_item_id=;
UPDATE ORDER_ITEMS SET quantity = WHERE order_id= AND line_item_id=;
COMMIT;
然後檢查 nfresults 表如下所示
SELECT TO_CHAR(operdate ddmonyy hh:mi:ss) operdate
rslt_msg FROM nfresults WHERE tblname = OEORDER_ITEMS;
輸出可能如下所示
OPERDATE RSLT_MSG
mar :: Not Found
您可能很奇怪為什麼只向 nfresults 表中插入了一行 – 畢竟您更新了 ORDER_ITEMS 表中的兩行實際上這兩個更新了的行具有相同的 order_id – 即它們屬於同一訂單此處我們假設客戶端應用程序將使用一個語句選擇訂單的所有訂單項因此它並不需要確切知道已經更改了某個訂單的哪些訂單項相反客戶端需要知道其中至少修改刪除或插入了一個訂單項的訂單 ID
構建客戶端
現在您已經針對 ORDERS 和 ORDER_ITEMS 表創建了注冊下面我們將了解一下訪問這些表中存儲的訂單及其訂單項的客戶端應用程序如何使用更改通知為此您可以構建一個 PHP 應用程序它將緩存針對以上表的查詢結果並采取相應的操作來響應有關對這些表所做更改的通知(從數據庫服務器中收到這些通知)一個簡單的方法是使用 PEAR::Cache_Lite 程序包它為您提供了一個可靠的機制來使緩存數據保持最新狀態尤其是您可以使用 Cache_Lite_Function 類(PEAR::Cache_Lite 程序包的一部分)通過該類您可以緩存函數調用
例如您可以創建一個函數來執行下列任務建立數據庫連接針對該數據庫執行 select 語句獲取檢索結果並最終以數組形式返回結果然後您可以通過 Cache_Lite_Function 實例的 call 方法緩存由該函數返回的結果數組以便可以從本地緩存而不是從後端數據庫讀取這些數組這樣可以顯著提高應用程序的性能然後在收到緩存數據更改的通知時您將使用 Cache_Lite_Function 實例的 drop 方法刪除緩存中的過期數據
回過頭來看看本文的示例您可能要創建兩個函數用於應用程序與數據庫交互第一個函數將查詢 ORDERS 表並返回具有指定 ID 的訂單而另一個函數將查詢 ORDER_ITEMS 表並返回該訂單的訂單項清單 顯示了包含 getOrderFields 函數(該函數接受訂單 ID 並返回一個包含所檢索到訂單的某些字段的關聯數組)的 getOrderFieldsphp 腳本
清單 獲取指定訂單的字段
<?php
//File:getOrderFieldsphp
require_once connectphp;
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;
$rsStatement = oci_parse($rsConnection$strSQL);
oci_bind_by_name($rsStatement :order_no $order_no );
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;
}
?>
清單 是 getOrderItemsphp 腳本該腳本包含 getOrderItems 函數該函數接受訂單 ID 並返回一個二維數組該數組包含表示訂單的訂單項的行
清單 獲取指定訂單的訂單項
<?php
//File:getOrderItemsphp
require_once connectphp;
function getOrderItems($order_no) {
if (!$rsConnection = GetConnection()){
return false;
}
$strSQL = SELECT * FROM ORDER_ITEMS WHERE
order_id =:order_no ORDER BY line_item_id;
$rsStatement = oci_parse($rsConnection$strSQL);
oci_bind_by_name($rsStatement :order_no $order_no );
if (!oci_execute($rsStatement)) {
$err = oci_error();
trigger_error(Query failed: $err[message]);
return false;
}
$nrows = oci_fetch_all($rsStatement $results);
return array ($nrows $results);
}
?>
注意以上兩個函數都需要 connectphp 腳本該腳本應包含返回數據庫連接的 GetConnection 函數清單 就是 connectphp 腳本
清單 獲取數據庫連接
<?php
//File:connectphp
function GetConnection() {
$dbHost = dbserverhost;
$dbHostPort=;
$dbServiceName = orclR;
$usr = oe;
$pswd = oe;
$dbConnStr = (DESCRIPTION=(ADDRESS=(PROTOCOL=TCP)(HOST=$dbHost)
(PORT=$dbHostPort))(CONNECT_DATA=(SERVICE_NAME=$dbServiceName)));
if(!$dbConn = oci_connect($usr$pswd$dbConnStr)) {
$err = oci_error();
trigger_error(Failed to connect $err[message]);
return false;
}
return $dbConn;
}
?>
現在您已經創建了與數據庫通信所需的所有函數下面我們將了解一下 Cache_Lite_Function 類的工作方式清單 是 testCachephp 腳本該腳本使用 Cache_Lite_Function 類緩存以上函數的結果
清單 使用 PEAR::Cache_Lite 緩存
<?php
//File:testCachephp
require_once getOrderItemsphp;
require_once getOrderFieldsphp;
require_once Cache/Lite/Functionphp;
$options = array(
cacheDir => /tmp/
lifeTime =>
);
if (!isset($_GET[order_no])) {
die(The order_no parameter is required);
}
$order_no=$_GET[order_no];
$cache = new Cache_Lite_Function($options);
if ($orderfields = $cache>call(getOrderFields $order_no)){
print <h>ORDER #$order_no</h>\n;
print <table>;
print <tr><td>DATE:</td><td>$orderfields[ORDER_DATE]</td></tr>;
print <tr><td>CUST_ID:</td><td>$orderfields[CUSTOMER_ID]</td></tr>;
print <tr><td>TOTAL:</td><td>$orderfields[ORDER_TOTAL]</td></tr>;
print </table>;
} else {
print Some problem occurred while getting order fields!\n;
$cache>drop(getOrderFields $order_no);
}
if (list($nrows $orderitems) = $cache>call(getOrderItems $order_no)){
//print <h>LINE ITEMS IN ORDER #$order_no</h>;
print <table border=>;
print <tr>\n;
while (list($key $value) = each($orderitems)) {
print <th>$key</th>\n;
}
print </tr>\n;
for ($i = ; $i < $nrows; $i++) {
print <tr>;
print <td>$orderitems[ORDER_ID][$i]</td>;
print <td>$orderitems[LINE_ITEM_ID][$i]</td>;
print <td>$orderitems[PRODUCT_ID][$i]</td>;
print <td>$orderitems[UNIT_PRICE][$i]</td>;
print <td>$orderitems[QUANTITY][$i]</td>;
print </tr>;
}
print </table>;
} else {
print Some problem occurred while getting order line items;
$cache>drop(getOrderItems $order_no);
}
?>
清單 中的 testCachephp 腳本應與 order_no URL 參數(代表 OEORDER 表中存儲的訂單 ID)一起被調用例如要檢索與 ID 為 的訂單相關的信息需要在浏覽器中輸入如下所示的 URL
結果浏覽器將生成以下輸出
ORDER #
DATE:
JUN AM
CUST_ID:
TOTAL:
ORDER_ID
LINE_ITEM_ID
PRODUCT_ID
UNIT_PRICE
QUANTITY
現在如果您單擊浏覽器中的 reload 按鈕testCachephp 腳本將不會再次調用 getOrderFields 和 getOrderItems 函數相反它將從本地緩存中讀取它們的結果因此從現在起的 小時(因為 lifeTime 設置為 秒)以內本地緩存即可滿足使用 order_no= 的每個 getOrderFields 或 getOrderItems 調用的需要但請注意Cache_Lite_Function 類未提供 API 來測試具有給定參數的給定函數是否存在可用緩存因此要確定每次使用相同參數調用函數時應用程序是實際上讀取緩存還是仍執行該函數可能有點棘手例如在以上示例中要確保緩存機制正常工作您可以臨時更改 connectphp 腳本中指定的連接信息以便它無法建立數據庫連接比如指定一個錯誤的數據庫服務器主機名稱然後再次使用 order_no= 運行 testCachephp 腳本如果緩存正常工作浏覽器的輸出應與先前的一樣
此外您還可以檢查緩存目錄該目錄作為 cacheDir 選項的值(在該示例中為 /tmp)傳遞給 Cache_Lite_Function 類的構造函數在該目錄中您將找到兩個剛創建的緩存文件這些文件的名稱類似於cache_bbbaeeadebddaec_addfcdfca注意如果您是一位 Windows 用戶則可能要使用 %SystemDrive%\temp 目錄保存緩存文件如果是這樣則必須將 cacheDir 選項設置為 /temp/
驗證緩存機制正常工作後可以接著創建一個 PHP 來處理從數據庫服務器收到的更改通知清單 是 dropResultphp 腳本數據庫服務器將調用該腳本來響應 ORDERS 和 ORDER_ITEMS 表的更改
清單 處理從數據庫服務器收到的更改通知
<?php
//File:dropResultsphp
require_once Cache/Lite/Functionphp;
$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]);
}
}
?>
創建 dropResultphp 腳本後請確保在通知處理程序中指定的 URL(如清單 所示)正確然後在 SQL*Plus 或類似工具中以 OE/OE 連接並執行 UPDATE 語句這些語句將影響本部分先前通過 testCachephp 腳本訪問的同一訂單(此處是 ID 為 的訂單)
UPDATE ORDERS SET order_mode = direct WHERE order_id=;
UPDATE ORDER_ITEMS SET quantity = WHERE order_id= AND line_item_id=;
UPDATE ORDER_ITEMS SET quantity = WHERE order_id= AND line_item_id=;
COMMIT;
為響應以上更新本文前面介紹的通知處理程序將逐個使用下列 URL 運行 dropResultsphp 腳本兩次
;table=ORDERS
;table=ORDER_ITEMS
從清單 中您可以清楚地看到dropResultphp 腳本在從數據庫服務器收到更改通知後並未刷新緩存它只是刪除了包含過期數據的緩存文件因此如果現在檢查緩存目錄則將看到在使用 order_no= 運行 testCachephp 腳本時創建的緩存文件已經消失這實際上意味著testCachephp 在下次請求與 ID 為 的訂單相關的數據時將從後端數據庫而非本地緩存中獲取該數據
您會發現在應用程序請求的結果集很有可能在應用程序使用它之前更改的情況下該方法將很有用就本文的示例而言這意味著與特定訂單相關的數據可能在 testCachephp 訪問該訂單之前多次更改這樣應用程序會因在從數據庫服務器收到更改通知後立即刷新它的緩存而做了大量不必要的工作
但如果您希望 dropResultphp 腳本在收到更改通知後立即刷新緩存則可以在調用 drop 方法後調用 Cache_Lite_Function 實例的 call 方法並為這兩個調用指定相同的參數在該情形下還應確保包含 getOrderFieldsphp 和 getOrderItemsphp 腳本以便 dropResultsphp 可以調用 getOrderFields 和 getOrderItems 函數來刷新緩存清單 是修改後的 dropResultphp 腳本
清單 在收到更改通知後立即刷新緩存
<?php
//File:dropResultsphp
require_once Cache/Lite/Functionphp;
require_once getOrderItemsphp;
require_once getOrderFieldsphp;
$options = array(
cacheDir => /tmp/
lifeTime =>
);
$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]);
$cache>call(getOrderItems $_GET[order_no]);
}
if ($_GET[table]==ORDERS){
$cache>drop(getOrderFields $_GET[order_no]);
$cache>call(getOrderFields $_GET[order_no]);
}
}
?>
如果存儲在 ORDERS 和 ORDER_ITEMS 表中的數據很少更改並且應用程序頻繁訪問它則以上方法可能很有用
如果 PHP 應用程序與 Oracle 數據庫 g 第 版交互則可以利用數據庫更改通知特性通過該特性應用程序可以接收通知來響應對與發出的請求關聯的對象進行的 DML 更改使用該特性您不必在特定時間段更新應用程序中的緩存相反僅當注冊查詢的結果集已經更改時才執行該操作
From:http://tw.wingwit.com/Article/program/Oracle/201311/18960.html