通过缓存数据库结果提高PHP性能(一)

通过缓存数据库结果提高PHP性能(一)

众所周知,缓存数据库查询的结果可以显著缩短脚本执行时间,并最大限度地减少数据库服务器上的负载。如果要处理的数据基本上是静态的,则该技术将非常有效。这是因为对远程数据库的许多数据请求最终可以从本地缓存得到满足,从而不必连接到数据库、执行查询以及获取结果。

但当您使用的数据库与Web服务器位于不同的计算机上时,缓存数据库结果集通常是一个不错的方法。不过,根据您的情况确定最佳的缓存策略却是一个难题。例如,对于使用最新数据库结果集比较重要的应用程序而言,时间触发的缓存方法(缓存系统常用的方法,它假设每次到达失效时间戳记时就重新生成缓存)可能并不是一个令人满意的解决方案。这种情况下,您需要采用一种机制,每当应用程序需要缓存的数据库数据发生更改时,该机制将通知该应用程序,以便该应用程序将缓存的过期数据与数据库保持一致。这种情况下使用“数据库更改通知”将非常方便。

“数据库更改通知”入门

“数据库更改通知”特性的用法非常简单:创建一个针对通知执行的通知处理程序–一个PL/SQL存储过程或客户端OCI回调函数。然后,针对要接收其更改通知的数据库对象注册一个查询,以便每当事务更改其中的任何对象并提交时调用通知处理程序。通常情况下,通知处理程序将被修改的表的名称、所做更改的类型以及所更改行的行ID(可选)发送给客户端监听程序,以便客户端应用程序可以在响应中执行相应的处理。

为了了解“数据库更改通知”特性的作用方式,请考虑以下示例。假设您的PHP应用程序访问OE.ORDERS表中存储的订单以及OE.ORDER_ITEMS中存储的订单项。鉴于很少更改已下订单的信息,您可能希望应用程序同时缓存针对ORDERS和ORDER_ITEMS表的查询结果集。要避免访问过期数据,您可以使用“数据库更改通知”,它可让您的应用程序方便地获知以上两个表中所存储数据的更改。

您必须先将CHANGENOTIFICATION系统权限以及EXECUTEONDBMS_CHANGENOTIFICATION权限授予OE用户,才能注册对ORDERS和ORDER_ITEMS表的查询,以便接收通知和响应对这两个表所做的DML或DDL更改。为此,可以从SQL命令行工具(如SQL*Plus)中执行下列命令。

CONNECT/ASSYSDBA;

GRANTCHANGENOTIFICATIONTOoe;

GRANTEXECUTEONDBMS_CHANGE_NOTIFICATIONTOoe;

确保将init.ora参数job_queue_processes设置为非零值,以便接收PL/SQL通知。或者,您也可以使用下面的ALTERSYSTEM命令:

ALTERSYSTEMSET"job_queue_processes"=2;然后,在以OE/OE连接后,您可以创建一个通知处理程序。但首先,您必须创建将由通知处理程序使用的数据库对象。例如,您可能需要创建一个或多个数据库表,以便通知处理程序将注册表的更改记录到其中。在以下示例中,您将创建nfresults表来记录以下信息:更改发生的日期和时间、被修改的表的名称以及一个消息(说明通知处理程序是否成功地将通知消息发送给客户端)。

CONNECToe/oe;

CREATETABLEnfresults(

operdateDATE,

tblnameVARCHAR2(60),

rslt_msgVARCHAR2(100)

);

在实际情况中,您可能需要创建更多表来记录通知事件以及所更改行的行ID等信息,但就本文而言,nfresults表完全可以满足需要。

使用UTL_HTTP向客户端发送通知

您可能还要创建一个或多个PL/SQL存储过程,并从通知处理程序中调用这些存储过程,从而实现一个更具可维护性和灵活性的解决方案。例如,您可能要创建一个实现将通知消息发送给客户端的存储过程。“清单1”是PL/SQL过程sendNotification。该过程使用UTL_HTTPPL程序包向客户端应用程序发送更改通知。

清单1.使用UTL_HTTP向客户端发送通知

CREATEORREPLACEPROCEDUREsendNotification(urlINVARCHAR2,

tblnameINVARCHAR2,order_idINVARCHAR2)IS

reqUTL_HTTP.REQ;

respUTL_HTTP.RESP;

err_msgVARCHAR2(100);

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

INSERTINTOnfresultsVALUES(SYSDATE,tblname,resp.reason_phrase);

UTL_HTTP.END_RESPONSE(resp);

EXCEPTIONWHENOTHERSTHEN

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

INSERTINTOnfresultsVALUES(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表所做更改的通知的通知处理程序

CREATEORREPLACEPROCEDUREorders_nf_callback(ntfndsINSYS.CHNF$_DESC)IS

tblnameVARCHAR2(60);

numtablesNUMBER;

event_typeNUMBER;

row_idVARCHAR2(20);

numrowsNUMBER;

ord_idVARCHAR2(12);

urlVARCHAR2(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

FORiIN1..numtablesLOOP

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;

ENDIF;

IF(tblname='OE.ORDERS')THEN

FORjIN1..numrowsLOOP

row_id:=ntfnds.table_desc_array(i).row_desc_array(j).row_id;

SELECTorder_idINTOord_idFROMordersWHERErowid=row_id;

sendNotification(url,tblname,ord_id);

ENDLOOP;

ENDIF;

ENDLOOP;

ENDIF;

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

REGDSSYS.CHNF$_REG_INFO;

regidNUMBER;

ord_idNUMBER;

qosflagsNUMBER;

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

SELECTorder_idINTOord_idFROMordersWHEREROWNUM<2;

DBMS_CHANGE_NOTIFICATION.REG_END;

END;

本示例针对ORDERS表创建了一个注册,并将orders_nf_callback用作通知处理程序。现在,如果您使用DML或DDL语句修改ORDERS表并提交事务,则将自动调用orders_nf_callback函数。例如,您可能针对ORDERS表执行下列UPDATE语句并提交该事务:

UPDATEORDERSSETorder_mode='direct'WHEREorder_id=2421;

UPDATEORDERSSETorder_mode='direct'WHEREorder_id=2422;

COMMIT;

要确保数据库发布了通知来响应以上事务,您可以检查nfresults表:

SELECTTO_CHAR(operdate,'dd-mon-yyhh:mi:ss')operdate,

tblname,rslt_msgFROMnfresults;

结果应如下所示:

OPERDATETBLNAMERSLT_MSG

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

02-mar-0604:31:28OE.ORDERSNotFound

02-mar-0604:31:29OE.ORDERSNotFound

从以上结果中可以清楚地看到,orders_nf_callback已经正常工作,但未找到客户端脚本。在该示例中出现这种情况并不意外,这是因为您并未创建URL中指定的dropResults.php脚本。有关dropResults.php脚本的说明,请参阅本文后面的构建客户端部分。