程序師世界是廣大編程愛好者互助、分享、學習的平台,程序師世界有你更精彩!
首頁
編程語言
C語言|JAVA編程
Python編程
網頁編程
ASP編程|PHP編程
JSP編程
數據庫知識
MYSQL數據庫|SqlServer數據庫
Oracle數據庫|DB2數據庫
 程式師世界 >> 數據庫知識 >> Oracle數據庫 >> Oracle數據庫基礎 >> 通過緩存數據庫結果提高PHP性能

通過緩存數據庫結果提高PHP性能

編輯:Oracle數據庫基礎
眾所周知,緩存數據庫查詢的結果可以顯著縮短腳本執行時間,並最大限度地減少數據庫服務器上的負載。如果要處理的數據基本上是靜態的,則該技術將非常有效。這是因為對遠程數據庫的許多數據請求最終可以從本地緩存得到滿足,從而不必連接到數據庫、執行查詢以及獲取結果。

  但當您使用的數據庫與 Web 服務器位於不同的計算機上時,緩存數據庫結果集通常是一個不錯的方法。不過,根據您的情況確定最佳的緩存策略卻是一個難題。例如,對於使用最新數據庫結果集比較重要的應用程序而言,時間觸發的緩存方法(緩存系統常用的方法,它假設每次到達失效時間戳記時就重新生成緩存)可能並不是一個令人滿意的解決方案。這種情況下,您需要采用一種機制,每當應用程序需要緩存的數據庫數據發生更改時,該機制將通知該應用程序,以便該應用程序將緩存的過期數據與數據庫保持一致。這種情況下使用“數據庫更改通知”將非常方便。

  “數據庫更改通知”入門

  “數據庫更改通知”特性的用法非常簡單:創建一個針對通知執行的通知處理程序 – 一個 PL/SQL 存儲過程或客戶端 OCI 回調函數。然後,針對要接收其更改通知的數據庫對象注冊一個查詢,以便每當事務更改其中的任何對象並提交時調用通知處理程序。通常情況下,通知處理程序將被修改的表的名稱、所做更改的類型以及所更改行的行 ID(可選)發送給客戶端監聽程序,以便客戶端應用程序可以在響應中執行相應的處理。

  為了了解“數據庫更改通知”特性的作用方式,請考慮以下示例。假設您的 PHP 應用程序訪問 OE.ORDERS 表中存儲的訂單以及 OE.ORDER_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; 

  確保將 init.ora 參數 job_queue_processes 設置為非零值,以便接收 PL/SQL 通知。或者,您也可以使用下面的 ALTER SYSTEM 命令:
ALTER SYSTEM SET "job_queue_processes"=2;
  然後,在以 OE/OE 連接後,您可以創建一個通知處理程序。但首先,您必須創建將由通知處理程序使用的數據庫對象。例如,您可能需要創建一個或多個數據庫表,以便通知處理程序將注冊表的更改記錄到其中。在以下示例中,您將創建 nfresults 表來記錄以下信息:更改發生的日期和時間、被修改的表的名稱以及一個消息(說明通知處理程序是否成功地將通知消息發送給客戶端)。
CONNECT oe/oe; 



CREATE TABLE nfresults ( 

operdate DATE,  

tblname VARCHAR2(60),  

rslt_msg VARCHAR2(100) 

); 

  在實際情況中,您可能需要創建更多表來記錄通知事件以及所更改行的行 ID 等信息,但就本文而言,nfresults 表完全可以滿足需要。

  使用 UTL_HTTP 向客戶端發送通知

  您可能還要創建一個或多個 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; 

err_msg VARCHAR2(100); 

tbl VARCHAR(60); 

BEGIN 

tbl:=SUBSTR(tblname, INSTR(tblname, '.', 1, 1)+1, 60); 

BEGIN 

req := UTL_HTTP.BEGIN_REQUEST(url||order_id||'&'||'table='||tbl); 

resp := UTL_HTTP.GET_RESPONSE(req); 

INSERT INTO nfresults VALUES(SYSDATE, tblname, resp.reason_phrase); 

UTL_HTTP.END_RESPONSE(resp); 

EXCEPTION WHEN OTHERS THEN 

err_msg := SUBSTR(SQLERRM, 1, 100); 

INSERT INTO nfresults VALUES(SYSDATE, tblname, err_msg); 

END; 

COMMIT; 

END; 

/ 

  如“清單 1”所示,sendNotification 以 UTL_HTTP.BEGIN_REQUEST 函數發出的 HTTP 請求的形式向客戶端發送通知消息。此 URL 包含 ORDERS 表中已更改行的 order_id。然後,它使用 UTL_HTTP.GET_RESPONSE 獲取客戶端發出的響應信息。實際上,sendNotification 並不需要處理客戶端返回的整個響應,而是只獲取一個在 RESP 記錄的 reason_phrase 字段中存儲的簡短消息(描述狀態代碼)。

眾所周知,緩存數據庫查詢的結果可以顯著縮短腳本執行時間,並最大限度地減少數據庫服務器上的負載。如果要處理的數據基本上是靜態的,則該技術將非常有效。這是因為對遠程數據庫的許多數據請求最終可以從本地緩存得到滿足,從而不必連接到數據庫、執行查詢以及獲取結果。

  但當您使用的數據庫與 Web 服務器位於不同的計算機上時,緩存數據庫結果集通常是一個不錯的方法。不過,根據您的情況確定最佳的緩存策略卻是一個難題。例如,對於使用最新數據庫結果集比較重要的應用程序而言,時間觸發的緩存方法(緩存系統常用的方法,它假設每次到達失效時間戳記時就重新生成緩存)可能並不是一個令人滿意的解決方案。這種情況下,您需要采用一種機制,每當應用程序需要緩存的數據庫數據發生更改時,該機制將通知該應用程序,以便該應用程序將緩存的過期數據與數據庫保持一致。這種情況下使用“數據庫更改通知”將非常方便。

  “數據庫更改通知”入門

  “數據庫更改通知”特性的用法非常簡單:創建一個針對通知執行的通知處理程序 – 一個 PL/SQL 存儲過程或客戶端 OCI 回調函數。然後,針對要接收其更改通知的數據庫對象注冊一個查詢,以便每當事務更改其中的任何對象並提交時調用通知處理程序。通常情況下,通知處理程序將被修改的表的名稱、所做更改的類型以及所更改行的行 ID(可選)發送給客戶端監聽程序,以便客戶端應用程序可以在響應中執行相應的處理。

  為了了解“數據庫更改通知”特性的作用方式,請考慮以下示例。假設您的 PHP 應用程序訪問 OE.ORDERS 表中存儲的訂單以及 OE.ORDER_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; 

  確保將 init.ora 參數 job_queue_processes 設置為非零值,以便接收 PL/SQL 通知。或者,您也可以使用下面的 ALTER SYSTEM 命令:
ALTER SYSTEM SET "job_queue_processes"=2;
  然後,在以 OE/OE 連接後,您可以創建一個通知處理程序。但首先,您必須創建將由通知處理程序使用的數據庫對象。例如,您可能需要創建一個或多個數據庫表,以便通知處理程序將注冊表的更改記錄到其中。在以下示例中,您將創建 nfresults 表來記錄以下信息:更改發生的日期和時間、被修改的表的名稱以及一個消息(說明通知處理程序是否成功地將通知消息發送給客戶端)。
CONNECT oe/oe; 



CREATE TABLE nfresults ( 

operdate DATE,  

tblname VARCHAR2(60),  

rslt_msg VARCHAR2(100) 

); 

  在實際情況中,您可能需要創建更多表來記錄通知事件以及所更改行的行 ID 等信息,但就本文而言,nfresults 表完全可以滿足需要。

  使用 UTL_HTTP 向客戶端發送通知

  您可能還要創建一個或多個 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; 

err_msg VARCHAR2(100); 

tbl VARCHAR(60); 

BEGIN 

tbl:=SUBSTR(tblname, INSTR(tblname, '.', 1, 1)+1, 60); 

BEGIN 

req := UTL_HTTP.BEGIN_REQUEST(url||order_id||'&'||'table='||tbl); 

resp := UTL_HTTP.GET_RESPONSE(req); 

INSERT INTO nfresults VALUES(SYSDATE, tblname, resp.reason_phrase); 

UTL_HTTP.END_RESPONSE(resp); 

EXCEPTION WHEN OTHERS THEN 

err_msg := SUBSTR(SQLERRM, 1, 100); 

INSERT INTO nfresults VALUES(SYSDATE, tblname, err_msg); 

END; 

COMMIT; 

END; 

/ 

  如“清單 1”所示,sendNotification 以 UTL_HTTP.BEGIN_REQUEST 函數發出的 HTTP 請求的形式向客戶端發送通知消息。此 URL 包含 ORDERS 表中已更改行的 order_id。然後,它使用 UTL_HTTP.GET_RESPONSE 獲取客戶端發出的響應信息。實際上,sendNotification 並不需要處理客戶端返回的整個響應,而是只獲取一個在 RESP 記錄的 reason_phrase 字段中存儲的簡短消息(描述狀態代碼)。

創建通知處理程序

  現在,您可以創建一個通知處理程序,它將借助於上面介紹的 sendNotification 過程向客戶端發送更改通知。來看一看“清單 2”中的 PL/SQL 過程 orders_nf_callback。

  清單 2. 處理對 OE.ORDERS 表所做更改的通知的通知處理程序
CREATE OR REPLACE PROCEDURE orders_nf_callback (ntfnds IN SYS.CHNF$_DESC) IS 

tblname VARCHAR2(60); 

numtables NUMBER; 

event_type NUMBER; 

row_id VARCHAR2(20); 

numrows NUMBER; 

ord_id VARCHAR2(12); 

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; 

END IF; 

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 orders WHERE rowid = row_id; 

sendNotification(url, tblname, ord_id);  

END LOOP; 

END IF; 

END LOOP; 

END IF; 

COMMIT; 

END; 

/ 

  如“清單 2”所示,此通知處理程序將 SYS.CHNF$_DESC 對象用作參數,然後使用它的屬性獲取該更改的詳細信息。在該示例中,此通知處理程序將只處理數據庫為響應對注冊對象進行的 DML 或 DDL 更改(也就是說,僅當通知類型為 EVENT_OBJCHANGE 時)而發布的通知,並忽略有關其他數據庫事件(如實例啟動或實例關閉)的通知。從以上版本開始,處理程序可以處理針對 OE.ORDERS 表中每個受影響的行發出的更改通知。在本文後面的“將表添加到現有注冊”部分中,您將向處理程序中添加幾行代碼,以便它可以處理針對 OE.ORDER_ITEMS 表中被修改的行發出的通知。

  為更改通知創建注冊

  創建通知處理程序後,必須為其創建一個查詢注冊。對於本示例而言,您必須在注冊過程中對 OE.ORDER 表執行查詢並將 orders_nf_callback 指定為通知處理程序。您還需要在 DBMS_CHANGE_NOTIFICATION 程序包中指定 QOS_ROWIDS 選項,以便在通知消息中啟用 ROWID 級別的粒度。“清單 3”是一個 PL/SQL 塊,它為 orders_nf_callback 通知處理程序創建查詢注冊。

  清單 3. 為通知處理程序創建查詢注冊
DECLARE 

REGDS SYS.CHNF$_REG_INFO; 

regid NUMBER; 

ord_id NUMBER; 

qosflags NUMBER; 

BEGIN 

qosflags := DBMS_CHANGE_NOTIFICATION.QOS_RELIABLE +  

DBMS_CHANGE_NOTIFICATION.QOS_ROWIDS; 

REGDS := SYS.CHNF$_REG_INFO ('orders_nf_callback', qosflags, 0,0,0); 

regid := DBMS_CHANGE_NOTIFICATION.NEW_REG_START (REGDS); 

SELECT order_id INTO ord_id FROM orders WHERE ROWNUM<2; 

DBMS_CHANGE_NOTIFICATION.REG_END; 

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 表:
SELECT TO_CHAR(operdate, 'dd-mon-yy hh:mi:ss') operdate,  

tblname, rslt_msg FROM nfresults; 

  結果應如下所示:
OPERDATE              TBLNAME     RSLT_MSG 

--------------------- ----------- --------- 

02-mar-06 04:31:28    OE.ORDERS   Not Found 

02-mar-06 04:31:29    OE.ORDERS   Not Found 

  從以上結果中可以清楚地看到,orders_nf_callback 已經正常工作,但未找到客戶端腳本。在該示例中出現這種情況並不意外,這是因為您並未創建 URL 中指定的 dropResults.php 腳本。有關 dropResults.PHP 腳本的說明,請參閱本文後面的構建客戶端 部分。

將表添加到現有注冊

  前一部分介紹了如何使用更改通知服務使數據庫在注冊對象(在以上示例中為 ORDERS 表)發生更改時發出通知。但從性能角度而言,客戶端應用程序可能更希望緩存 ORDER_ITEMS 表而非 ORDERS 表本身的查詢結果集,這是因為它在每次訪問訂單時,不得不從 ORDERS 表中只檢索一行,但同時必須從 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 函數將一個新對象添加到該注冊,如下所示:
DECLARE 

ord_id NUMBER; 

BEGIN 

DBMS_CHANGE_NOTIFICATION.ENABLE_REG(241); 

SELECT order_id INTO ord_id FROM order_items WHERE ROWNUM < 2; DBMS_CHANGE_NOTIFICATION.REG_END; END; / 
  完成了!從現在開始,數據庫將生成一個通知來響應對 ORDERS 和 ORDER_ITEMS 所做的任何更改,並調用 orders_nf_callback 過程來處理通知。因此,下一步就是編輯 orders_nf_callback,以便它可以處理因對 ORDER_ITEMS 表執行 DML 操作而生成的通知。但在重新創建 orders_nf_callback 過程之前,您需要創建以下將在更新過程中引用的表類型: 
CREATE TYPE rdesc_tab AS TABLE OF SYS.CHNF$_RDESC;
  然後,返回清單 2,在以下代碼行之後:
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 orders WHERE rowid = row_id; 

sendNotification(url, tblname, ord_id);  

END LOOP; 

END IF; 

  插入以下代碼:
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) 

LOOP 

sendNotification(url, tblname, rec.o_id);  

END LOOP; 

END IF; 

  重新創建 orders_nf_callback 後,您需要測試它能否正常工作。為此,您可以針對 ORDER_ITEMS 表執行下列 UPDATE 語句並提交該事務:
UPDATE ORDER_ITEMS SET quantity = 160 WHERE order_id=2421 AND line_item_id=1; 

UPDATE ORDER_ITEMS SET quantity = 160 WHERE order_id=2421 AND line_item_id=2; 

COMMIT; 

  然後,檢查 nfresults 表,如下所示:
SELECT TO_CHAR(operdate, 'dd-mon-yy hh:mi:ss') operdate,  

rslt_msg FROM nfresults WHERE tblname = 'OE.ORDER_ITEMS';
  輸出可能如下所示:
OPERDATE            RSLT_MSG 

------------------- -------------- 

03-mar-06 12:32:27  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 表並返回該訂單的訂單項。“清單 4”顯示了包含 getOrderFields 函數(該函數接受訂單 ID 並返回一個包含所檢索到訂單的某些字段的關聯數組)的 getOrderFIElds.PHP 腳本。

  清單 4. 獲取指定訂單的字段

<?php 

//File:getOrderFIElds.php 

require_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"; 

$rsStatement = oci_parse($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 並返回一個二維數組,該數組包含表示訂單的訂單項的行。

  清單 5. 獲取指定訂單的訂單項
<?php 

//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"; 

$rsStatement = oci_parse($rsConnection,$strSQL); 

oci_bind_by_name($rsStatement, ":order_no", $order_no, 12); 

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); 

 } 

?> 

  注意,以上兩個函數都需要 connect.PHP 腳本,該腳本應包含返回數據庫連接的 GetConnection 函數。清單 6 就是 connect.PHP 腳本:

  清單 6. 獲取數據庫連接
<?php 

//File:connect.PHP 

function GetConnection() { 

$dbHost = "dbserverhost"; 

$dbHostPort="1521"; 

$dbServiceName = "orclR2"; 

$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 類的工作方式。清單 7 是 testCache.PHP 腳本,該腳本使用 Cache_Lite_Function 類緩存以上函數的結果。

  清單 7. 使用 PEAR::Cache_Lite 緩存
<?php 

//File:testCache.php 

require_once 'getOrderItems.php'; 

require_once 'getOrderFIElds.php'; 

require_once 'Cache/Lite/Function.PHP'; 



$options = array(  

'cacheDir' => '/tmp/',  

'lifeTime' => 86400  

 );  



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 "<h3>ORDER #$order_no</h3>\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 "<h3>LINE ITEMS IN ORDER #$order_no</h3>"; 

print "<table border=1>"; 

print "<tr>\n"; 

while (list($key, $value) = each($orderitems)) { 

print "<th>$key</th>\n"; 

  } 

print "</tr>\n"; 

for ($i = 0; $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); 

 } 

?> 

  “清單 7”中的 testCache.PHP 腳本應與 order_no URL 參數(代表 OE.ORDER 表中存儲的訂單 ID)一起被調用。例如,要檢索與 ID 為 2408 的訂單相關的信息,需要在浏覽器中輸入如下所示的 URL:
http://webserverhost/phpcache/testCache.PHP?order_no=2408
  結果,浏覽器將生成以下輸出:

ORDER #2408
DATE: 29-JUN-99 06.59.31.333617 AM CUST_ID: 166 TOTAL: 309
ORDER_ID LINE_ITEM_ID PRODUCT_ID UNIT_PRICE QUANTITY 2408 1 2751 61 32408 2 2761 26 1 2408 3 2783 10 10
  現在,如果您單擊浏覽器中的 reload 按鈕,testCache.php 腳本將不會再次調用 getOrderFields 和 getOrderItems 函數。相反,它將從本地緩存中讀取它們的結果。因此,從現在起的 24 小時(因為 lifeTime 設置為 86400 秒)以內,本地緩存即可滿足使用 order_no=2108 的每個 getOrderFIElds 或 getOrderItems 調用的需要。但請注意,Cache_Lite_Function 類未提供 API 來測試具有給定參數的給定函數是否存在可用緩存。因此,要確定每次使用相同參數調用函數時應用程序是實際上讀取緩存還是仍執行該函數可能有點棘手。例如,在以上示例中,要確保緩存機制正常工作,您可以臨時更改 connect.PHP 腳本中指定的連接信息,以便它無法建立數據庫連接;比如指定一個錯誤的數據庫服務器主機名稱,然後再次使用 order_no=2108 運行 testCache.PHP 腳本。如果緩存正常工作,浏覽器的輸出應與先前的一樣。

  此外,您還可以檢查緩存目錄,該目錄作為 cacheDir 選項的值(在該示例中為 /tmp)傳遞給 Cache_Lite_Function 類的構造函數。在該目錄中,您將找到兩個剛創建的緩存文件,這些文件的名稱類似於:cache_7b181b55b55aee36ad5e7bd9d5a091ec_3ad04d3024f4cd54296f75c92a359154。注意,如果您是一位 Windows 用戶,則可能要使用 %SystemDrive%\temp 目錄保存緩存文件。如果是這樣,則必須將 cacheDir 選項設置為 /temp/。

  驗證緩存機制正常工作後,可以接著創建一個 PHP 來處理從數據庫服務器收到的更改通知。“清單 8”是 dropResult.PHP 腳本。數據庫服務器將調用該腳本來響應 ORDERS 和 ORDER_ITEMS 表的更改。

  清單 8. 處理從數據庫服務器收到的更改通知
<?php 

//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 腳本後,請確保在通知處理程序中指定的 URL(如清單 2 所示)正確。然後,在 SQL*Plus 或類似工具中以 OE/OE 連接,並執行 UPDATE 語句,這些語句將影響本部分先前通過 testCache.PHP 腳本訪問的同一訂單(此處是 ID 為 2408 的訂單):
UPDATE ORDERS SET order_mode = 'direct' WHERE order_id=2408; 

UPDATE ORDER_ITEMS SET quantity = 3 WHERE order_id=2408 AND line_item_id=1; 

UPDATE ORDER_ITEMS SET quantity = 1 WHERE order_id=2408 AND line_item_id=2; 

COMMIT; 

  
  為響應以上更新,本文前面介紹的通知處理程序將逐個使用下列 URL 運行 dropResults.PHP 腳本兩次:




http://webserverhost/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 方法,並為這兩個調用指定相同的參數。在該情形下,還應確保包含 getOrderFields.php 和 getOrderItems.php 腳本,以便 dropResults.php 可以調用 getOrderFIElds 和 getOrderItems 函數來刷新緩存。“清單 9”是修改後的 dropResult.PHP 腳本。

  清單 9. 在收到更改通知後立即刷新緩存
<?php 

//File:dropResults.php 

require_once 'Cache/Lite/Function.php'; 

require_once 'getOrderItems.php'; 

require_once 'getOrderFIElds.PHP'; 



$options = array( 

'cacheDir' => '/tmp/', 

'lifeTime' => 86400  

 ); 

$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 數據庫 10g 第 2 版交互,則可以利用“數據庫更改通知特性”,通過該特性應用程序可以接收通知來響應對與發出的請求關聯的對象進行的 DML 更改。使用該特性,您不必在特定時間段更新應用程序中的緩存。相反,僅當注冊查詢的結果集已經更改時才執行該操作。
  1. 上一頁:
  2. 下一頁:
Copyright © 程式師世界 All Rights Reserved