通过缓存数据库结果提高PHP性能的原理介绍
前端之家收集整理的这篇文章主要介绍了
通过缓存数据库结果提高PHP性能的原理介绍,
前端之家小编觉得挺不错的,现在分享给大家,也给大家做个参考。
但当您使用的数据库与 Web 服务器位于不同的计算机上时,缓存数据库结果集通常是一个不错的方法。不过,根据您的情况确定最佳的缓存策略却是一个难题。例如,对于使用最新数据库结果集比较重要的应用程序而言,时间触发的缓存方法(缓存系统常用的方法,它假设每次到达失效时间戳记时就重新生成缓存)可能并不是一个令人满意的解决方案。这种情况下,您需要采用一种机制,每当应用程序需要缓存的数据库数据发生更改时,该机制将通知该应用程序,以便该应用程序将缓存的过期数据与数据库保持一致。这种情况下使用“数据库更改通知”(一个新的 Oracle 数据库 10g 第 2 版特性)将非常方便。 “数据库更改通知”入门 “数据库更改通知”特性的用法非常简单:创建一个针对通知执行的通知处理程序 – 一个 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 向客户端发送通知 <div class="codetitle"><a style="CURSOR: pointer" data="94477" class="copybut" id="copybut94477" onclick="doCopy('code94477')"> 代码如下:
<div class="codebody" id="code94477">
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,100);
INSERT INTO nfresults VALUES(SYSDATE,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 表所做更改的
通知的
通知处理程序 <div class="codetitle">
<a style="CURSOR: pointer" data="86191" class="copybut" id="copybut86191" onclick="doCopy('code86191')"> 代码如下: <div class="codebody" id="code86191">
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,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. 为
通知处理程序创建
查询注册 <div class="codetitle">
<a style="CURSOR: pointer" data="64165" class="copybut" id="copybut64165" onclick="doCopy('code64165')"> 代码如下: <div class="codebody" id="code64165">
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);
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 脚本。
将表
添加到现有
注册 前一部分介绍了如何使用更改
通知服务使
数据库在
注册对象(在以上示例中为 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
函数将一个新对象
添加到该
注册,如下所示:
<div class="codetitle">
<a style="CURSOR: pointer" data="3686" class="copybut" id="copybut3686" onclick="doCopy('code3686')"> 代码如下: <div class="codebody" id="code3686">