通过缓存数据库结果提高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 向客户端发送通知

代码如下:

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 脚本。
  将表添加到现有注册
  前一部分介绍了如何使用更改通知服务使数据库在注册对象(在以上示例中为 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;  然后,返回清单,在以下代码行之后:


代码如下:

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 3
2408 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 更改。使用该特性,您不必在特定时间段更新应用程序中的缓存。相反,仅当注册查询的结果集已经更改时才执行该操作。

(0)

相关推荐

  • PHP性能优化 产生高度优化代码

    1.将PHP升级到最新版 提高性能的最简单的方式是不断升级.更新PHP版本. 2.使用分析器 网站运行缓慢的原因颇多,Web应用程序极其复杂,让人扑朔迷离.而一种可能性在于PHP代码本身.这个分析器可以帮助你快速找出造成瓶颈的代码,提高网站运行的总体性能. Xdebug PHP extension提供了强大的功能,可以用来调试,也可以用来分析代码.方便开发人员直接跟踪脚本的执行,实时查看综合数据.还可以将这个数据导入到可视化的工具 KCachegrind中. 3.检错报告 PHP支持强大的检错功

  • 提升PHP性能的21种方法介绍

    1.用单引号来包含字符串要比双引号来包含字符串更快一些.因为PHP会在双引号包围的字符串中搜寻变量,单引号则不会.2.如果能将类的方法定义成static,就尽量定义成static,它的速度会提升将近4倍.3.$row['id'] 的速度是$row[id]的7倍.4.echo 比 print 快,并且使用echo的多重参数(译注:指用逗号而不是句点)代替字符串连接,比如echo $str1,$str2.5.在执行for循环之前确定最大循环数,不要每循环一次都计算最大值,最好运用foreach代替.

  • 提高PHP编程效率的53个要点(经验小结)

    用单引号代替双引号来包含字符串,这样做会更快一些.因为PHP会在双引号包围的字符串中搜寻变量,单引号则不会,注意:只有echo能这么做,它是一种可以把多个字符串当作参数的"函数"(译注:PHP手册中说echo是语言结构,不是真正的函数,故把函数加上了双引号). 1.如果能将类的方法定义成static,就尽量定义成static,它的速度会提升将近4倍. 2.$row['id'] 的速度是$row[id]的7倍. 3.echo 比 print 快,并且使用echo的多重参数(译注:指用逗号

  • 优化php效率,提高php性能的一些方法

    1.在可以用file_get_contents替代file.fopen.feof.fgets等系列方法的情况下,尽量用 file_get_contents,因为他的效率高得多!但是要注意file_get_contents在打开一个URL文件时候的PHP版本问题: 2.尽量的少进行文件操作,虽然PHP的文件操作效率也不低的: 3.优化Select SQL语句,在可能的情况下尽量少的进行Insert.Update操作(在update上,我被恶批过): 4.尽可能的使用PHP内部函数(但是我却为了找个

  • 有关PHP性能优化的介绍

    PHP优化对于PHP的优化主要是对php.ini中的相关主要参数进行合理调整和设置,以下我们就来看看php.ini中的一些对性能影响较大的参数应该如何设置. # vi /etc/php.ini(1) PHP函数禁用找到:disable_functions =该选项可以设置哪些PHP函数是禁止使用的,PHP中有一些函数的风险性还是相当大的,可以直接执行一些系统级脚本命令,如果允许这些函数执行,当PHP程序出现漏洞时,损失是非常严重的!以下我们给出推荐的禁用函数设置:disable_function

  • 通过缓存数据库结果提高PHP性能的原理介绍

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

  • 使用v-memo缓存模板子树提高应用性能详解

    目录 引言 v-memo 是做什么的? 事例 错误的使用方式 管理更新 与 v-for 结合使用 无意中停止了子组件触发的更新 总结 引言 Vue3 为我们提供了几项开箱即用的重大性能改进,但也引入了一些额外的手动功能,可以帮助提高我们的应用性能. 在这节课中,我们介绍一下,在 Vue 3.2 中引入新指令 v-memo.引入这个指令的目的是帮助我们提高中型/大型 Vue 应用程序的性能,小项目大家根据需要自行决定. v-memo 是做什么的? 官网对 v-memo 定义是这样的: 缓存一个模板

  • 提高jQuery性能优化的技巧

    下面把提高jQuery性能优化技巧给大家分享如下: 缓存变量 DOM遍历是昂贵的,所以尽量将会重用的元素缓存. 复制代码 代码如下: // 糟糕 h = $('#element').height(); $('#element').css('height',h-20); // 建议 $element = $('#element'); h = $element.height(); $element.css('height',h-20); 避免全局变量 jQuery与javascript一样,一般来说

  • 充分利用ASP.NET的三种缓存提高站点性能的注意方法

    ASP.NET提供三种主要形式的缓存:页面级输出缓存.用户控件级输出缓存(或称为片段缓存)和缓存API. 尽早缓存:经常缓存  您应该在应用程序的每一层都实现缓存.向数据层.业务逻辑层.UI或输出层添加缓存支持.内存现在非常便宜-因此,通过以智能的方式在整个应用程序中实现缓存,可以获得很大的性能提高. 页面级输出缓存 最简单的缓存形式,只是在内存中保留为响应请求而发送的HTML的副本. 要实现页面输出缓存,只要将一条OutputCache指令添加到页面即可. <%@ OutputCache Du

  • 在DB2中提高INSERT性能的技巧(1)

    正在看的db2教程是:在DB2中提高INSERT性能的技巧(1). INSERT 处理过程概述 首先让我们快速地看看插入一行时的处理步骤.这些步骤中的每一步都有优化的潜力,对此我们在后面会一一讨论. 在客户机准备 语句.对于动态 SQL,在语句执行前就要做这一步,此处的性能是很重要的:对于静态 SQL,这一步的性能实际上关系不大,因为语句的准备是事先完成的. 在客户机,将要插入的行的各个 列值组装起来,发送到 DB2 服务器. DB2 服务器确定将这一行插入到哪一页中. DB2 在 用于该页的缓

  • mysql+Spring数据库隔离级别与性能分析

    这里以mysql为例,先明确以下几个问题: 一.一般项目如果不自己配置事务的话,一般默认的是autocommit,即执行完一个操作后自动commit,提交事务. (注:事务是绑定在数据库操作上的,也就是当程序执行(statement.excute等操作)转而到数据库层面上的时候,事务才开始发生)当然spring可以将几个数据库操作动作绑在一个事务中,这样就需要介绍下spring事务配置方法,下面介绍的是常用方法,其他方法网上有很多.spring提供了很多事务配置的策略,很方便,简要介绍一下: 复

  • SQL Server 聚焦存储过程性能优化、数据压缩和页压缩提高IO性能方法(一)

    前言 关于SQL Server基础系列尚未结束,还剩下最后一点内容未写,后面会继续.有园友询问我什么时候开始写SQL Server性能系列,估计还得等一段时间,最近工作也比较忙,但是会陆陆续续的更新SQL Server性能系列,本篇作为性能系列的基本引导,让大家尝尝鲜.在涉及到SQL Server性能优化时,我看到的有些文章就是一上来列出SQL Server的性能优化条例,根本没有弄清楚为什么这么做,当然也有可能是自己弄懂了,只是作为备忘录,但是到了我这里,我会遵循不仅仅是备忘录,还要让各位园友

  • php提高脚本性能的4个技巧

    通常,我使用明显的常规PHP函数编写代码来解决相应的问题.但是对于其中的一些问题,我遇到了一些替代解决方案,这些解决方案特别提高了性能. 在本文中,我想介绍一些替代方案.如果您正在寻找可能减少生产中执行时间的可能性,这将很有用.让我们看看,哪种PHP方法可能会被性能更高的方法所取代,以及是否存在成本或折衷的问题. 1.删除重复项 您有一个包含重复项的大型数组,并且希望删除它们,使其仅具有唯一值的数组. 常规 array_unique($array); 替代 array_keys(array_fl

  • EF Core通过显式编译提高查询性能

    今天,我将向您展示这些EF Core中一个很酷的功能,通过使用显式编译的查询,提高查询性能. 不过在介绍具体内容之前,需要说明一点,EF Core已经对表达式的编译使用了缓存:当您的代码需要重用以前执行的查询时,EF Core将使用哈希查找并从缓存中返回已编译的查询. 不过,您可能希望直接对查询进行编译,跳过哈希的计算和缓存查找.我们可以通过在EF静态类中下面两个方法来实现: EF.CompileQuery() EF.CompileAsyncQuery() 这些方法允许您定义一个已编译的查询,然

  • 提高代码性能技巧谈—以创建千行表格为例

    微软的开发周期中很重要的一块是调整产品的性能.性能调整也是开发者应当留心的关键部分之一. 经过多年发展,业界对于如何优化Win32程序性能已经有非常多的了解. 现在开发者遇到的问题之一是不太清楚是什么导致DTHML和HTML页面运行快或者慢.当然,有一些很简单的方法--比如不要使用2MB大的图片.我们曾经使用过另外一些有趣的技巧提高了DHTML页面的性能,希望它们能帮助你改善自己的页面性能. 这里我使用了一个建立Table的程序例子.其中用document.createElement()和ele

随机推荐