科技行者

行者学院 转型私董会 科技行者专题报道 网红大战科技行者

知识库

知识库 安全导航

至顶网软件频道应用软件通过缓存数据库结果提高PHP性能

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

  • 扫一扫
    分享文章到微信

  • 扫一扫
    关注官方公众号
    至顶头条

众所周知,缓存数据库查询的结果可以显著缩短脚本执行时间,并最大限度地减少数据库服务器上的负载.

作者:Yuli Vasiliev 来源:Oracle技术网 2007年10月21日

关键字:

  • 评论
  • 分享微博
  • 分享邮件
构建客户端

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

查看本文来源

    • 评论
    • 分享微博
    • 分享邮件
    邮件订阅

    如果您非常迫切的想了解IT领域最新产品与技术信息,那么订阅至顶网技术邮件将是您的最佳途径之一。

    重磅专题
    往期文章
    最新文章