作者:Christopher Jones
学习如何在 PHP 应用程序中使用 Oracle 数据库的“客户端标识符”特性。
2010 年 9 月发布
PHP 语言的 OCI8 扩展允许应用程序对每个数据库连接设置一个小字符串标识符令牌。Oracle 数据库可以使用这种“客户端标识符”来区分使用一组通用数据库凭证连接到数据库的各个 Web 应用程序用户。例如,某个 Web 站点中的每个页面都可作为同一个数据库用户 PHPUSER 物理连接到数据库。如果两个不同的用户“Chris”和“Alison”都在使用该站点,则可以将这两个用户名设置为其各自的客户端标识符,将这些标识符传递给数据库。
通过将每个独一无二的客户端标识符与每个相应的 Web 用户关联,Oracle 数据库能够:
典型 Oracle PHP 应用程序中的每个 PHP 文件都会使用相同的数据库用户名调用 oci_connect()。一旦应用程序自己的身份验证系统确定某个特定 Web 用户没有问题之后,会通过 HTTP 响应和请求来回传递一个独特的令牌,这样 Web 用户不必在每次载入新 Web 页面时重新进行身份验证。
实现应用程序级身份验证和传递 PHP 会话信息需要谨慎的设计以确保安全性。在应用程序中设置客户端标识符还需要仔细确保一致的使用。无状态 Web 应用程序利用共享数据库连接这一共性意味着应用程序代码完整性是确保数据安全性的重要组成部分。本文就何时设置客户端标识符、如何使用它们协助 Web 站点的开发和管理提供了建议。本文不包含 PHP 身份验证或会话处理最佳实践。
应该在连接之后、为 Web 用户执行任何语句或 OCI8 调用之前使用 oci_set_client_identifier() 设置客户端标识符。在最基本的层面上,客户端标识符可以是由之前的身份验证请求存储在 PHP 会话数据中的 Web 用户名称:
session_start(); $c = oci_pconnect('phpuser', 'welcome', 'localhost/orcl'); oci_set_client_identifier($c, $_SESSION['app_user_name']); . . .
如果最终用户的身份在脚本运行时发生了更改(例如当 PHP 在执行长时间运行的命令行进程时,或者在一个管理 Web 页面中运行不同的组件,这些组件代表不同的最终用户),即可在最终用户身份变化的每个点调用 oci_set_client_identifier():
$c = oci_pconnect('phpuser', 'welcome', 'localhost/orcl'); $myuser = 'Chris'; oci_set_client_identifier($c, $myuser); . . . $myuser = 'Alison'; oci_set_client_identifier($c, $myuser); . . .
在实践中,应考虑为标识符使用更安全的值。
使用 oci_connect()、oci_new_connect() 或 oci_pconnect() 连接调用时,可以设置客户端标识符。当数据库被配置为使用以下三种服务器进程中的任意一种时,可以使用客户端标识符:“专用”服务器、“共享”服务器,以及在使用数据库驻留连接池 (DRCP) 服务器的时候。
PHP OCI8 1.4(最初包括在 PHP 5.3.1 中)中增加了 oci_set_client_identifier() 函数。在旧版本的 OCI8 中,您可以使用 PL/SQL DBMS_SESSION 程序包代替:
session_start(); $c = oci_pconnect('phpuser', 'welcome', 'localhost/orcl'); $s = oci_parse($c, "begin dbms_session.set_identifier(:id); end;"); oci_bind_by_name($s, ":id", $_SESSION['app_user_name']); oci_execute($s);
最好使用 oci_set_client_identifier() 函数,因为它不同于 DBMS_SESSION.SET_IDENTIFIER,不会强制数据库往返发送请求和响应。如果使用 oci_set_client_identifier,可通过任何实际从 PHP 抵达数据库的后续 OCI8 调用捎带客户端标识符。不必要的往返发送会降低每个 PHP 页面的速度,影响应用程序的可伸缩性。
要在 PHP 框架中设置客户端标识符,您目前需要直接调用 OCI8。在 Zend 框架中,从概念上来说,这应该类似于:
$auth = Zend_Auth::getInstance(); if ($auth->hasIdentity()) { $db = $this->getAdapter(); $conn = $db->getConnection(); if ($conn) { oci_set_client_identifier($conn, $auth->getIdentity()->USERNAME); } }
在实践中,最好实行职责分离。
PHP OCI8 不会在 HTTP 请求结束时清除客户端标识符,因为通过往返发送清除该值的开销会影响每一个应用程序的可伸缩性。对于标准 OCI8 连接来说这并无不利之处,因为数据库连接将在 HTTP 请求结束时销毁,进而标识符值也会清除。但对于使用持久连接的 Web 请求,标识符可能会保持有效。为了避免数据库记录到不正确的标识符或者未记录到标识符,连接到数据库的所有 PHP 文件都应该设置标识符,从而保证其在请求执行期间的正确性。如果这一点得不到保证,那么每个设置客户端标识符的脚本都应在脚本末尾处强制清除,如下所示:
$s = oci_parse($c, "begin dbms_session.clear_identifier; end;"); oci_execute($s);
这将导致与数据库的往返发送。
PHP 的 oci_set_client_identifier() 函数相当于设置 Oracle 的 C 级 OCI_ATTR_CLIENT_IDENTIFIER 属性。有关这方面以及与此相当的 PL/SQL DBMS_SESSION.SET_IDENTIFIER 的 Oracle 文章提供了有关客户端标识符的出色参考。
示例 PHP“Parts”应用程序展示了如何在 OCI8 扩展中使用客户端标识符。总而言之,该应用程序显示电子和水暖配件库存。应用程序级身份验证系统处理 Web 用户登录。每个 Web 用户成功登录后,其独特的标识符将在 PHP 会话数据的 HTTP 请求之间传递。它将用作客户端标识符值。为使这里讨论的 Oracle 技术不是那么抽象难懂,该应用程序具有足够的复杂度,但该应用程序绝非 生产示例。该示例应用程序的目的仅仅是展示 Web 用户与数据库用户之间的关系,展示如何在数据库中使用客户端标识符。PHP 会话管理需要谨慎设计以便将安全性问题降至最低。有许多外部参考资料讨论了这个问题,每一位 PHP 开发人员都应仔细研读这些参考资料。Michael McLaughlin 的基于数据库的 PHP 应用程序身份验证是一个良好起点,您可以先阅读这篇文章,再阅读其他资料。
Parts 应用程序的核心是 setup.sql 文件,该文件创建数据库对象。该应用程序中的所有 PHP 脚本都将连接到使用 PHPUSER 模式的数据库,该数据库拥有 PARTS 应用程序表。该 SQL 脚本创建第二个用户 PHP_SEC_ADMIN 来存放应用程序的相关安全性信息。此用户将获得审计示例所需的额外数据库权限,如下。PHP_AUTHENTICATION 表包含应用程序用户名和口令。将授予 PHPUSER 用户对此表的查询访问权限,这样 PHP 应用程序只能打开一个数据库连接,但该连接无法修改安全性信息。
-- setup.sql set echo on -- Create PHP application user connect system/welcome -- Create the PHP application user drop user phpuser cascade; create user phpuser identified by welcome; grant connect, resource to phpuser; alter user phpuser default tablespace users temporary tablespace temp account unlock; -- Create user owner security information about the application drop user php_sec_admin cascade; create user php_sec_admin identified by welcome; alter user php_sec_admin default tablespace system temporary tablespace temp account unlock; grant create procedure, create session, create table, resource, select any dictionary to php_sec_admin; connect phpuser/welcome -- "Parts" table for the application demo create table parts (id number primary key, category varchar2(20), name varchar2(20)); insert into parts values (1, 'electrical', 'lamp'); insert into parts values (2, 'electrical', 'wire'); insert into parts values (3, 'electrical', 'switch'); insert into parts values (4, 'plumbing', 'pipe'); insert into parts values (5, 'plumbing', 'sink'); insert into parts values (6, 'plumbing', 'toilet'); commit; connect php_sec_admin/welcome -- Authentication table with the web user user names & passwords. -- A real application would NEVER store plain-text passwords but this -- article is about uses of client identifiers and not about -- authentication. create table php_authentication (app_username varchar2(20) primary key, app_password varchar2(20) not null); insert into php_authentication values ('chris', 'tiger'); insert into php_authentication values ('alison', 'red'); commit; grant select on php_authentication to phpuser;
生产应用程序不会使用这么简单的口令,也永远不会 在表中存储明文口令。应用程序可以通过多种方法执行最终用户身份验证,包括使用 LDAP。
PHP 应用程序中的每个脚本都需要知道 Oracle DB 凭证,因而这些凭证存储在一个通用的 include 文件 dbinfo.inc.php 中:
<?php // dbinfo.inc.php // All connections to the database use these credentials define("ORA_CON_UN", "phpuser"); define("ORA_CON_PW", "welcome"); define("ORA_CON_DB", "localhost/orcl"); ?>
在现实中,请考虑使用 Oracle Wallet Manager 并使用 OCI_CRED_EXT 连接,而不要硬编码数据库口令。只有可进行应用程序管理的操作系统帐户以及 Web 服务器进程的所有者才应具备钱夹的访问权限。
应用程序登录页面 login.php 是一个典型的简单 PHP 脚本,初次载入时会显示一个表单:
login.php 文件类似于:
<?php // login.php require_once('./dbinfo.inc.php'); session_start(); function login_form($message) { echo <<<EOD <body style="font-family: Arial, sans-serif;"> <h2>Login Page</h2> <p>$message</p> <form action="login.php" method="POST"> <p>Username: <input type="text" name="username"></p> <p>Password: <input type="text" name="password"</p> <input type="submit" value="Login"> </form> </body> EOD; } if (!isset($_POST['username']) || !isset($_POST['password'])) { login_form('Welcome'); } else { // Check validity of the supplied username & password $c = oci_pconnect(ORA_CON_UN, ORA_CON_PW, ORA_CON_DB); // Use a "bootstrap" identifier for this administration page oci_set_client_identifier($c, 'admin'); $s = oci_parse($c, 'select app_username from php_sec_admin.php_authentication where app_username = :un_bv and app_password = :pw_bv'); oci_bind_by_name($s, ":un_bv", $_POST['username']); oci_bind_by_name($s, ":pw_bv", $_POST['password']); oci_execute($s); $r = oci_fetch_array($s, OCI_ASSOC); if ($r) { // The password matches: the user can use the application // Set the user name to be used as the client identifier in // future HTTP requests: $_SESSION['username'] = $_POST['username']; echo <<<EOD <body style="font-family: Arial, sans-serif;"> <h2>Login was successful</h2> <p><a href="application.php">Run the Application</a><p> </body> EOD; exit; } else { // No rows matched so login failed login_form('Login failed. Valid usernames/passwords ' . 'are "chris/tiger" and "alison/red"'); } } ?>
如果复制这段代码,请确保“heredoc”EOD 令牌位于代码行的开始处。
对于表单提交,该脚本会调用自身,根据 PHP_AUTHENTICATION 表中的用户验证所输入的用户名和口令。admin 客户端标识符设置为引导值,因为在这个初始时间点,我们还不了解最终用户是否有效。该登录脚本还是一个管理性组件,不会为最终用户执行任何实际应用程序工作。
通过该登录页面,经过身份验证的用户可以单击进入应用程序库存页面:
在 PHP 会话数据中传递给应用程序页面的用户名是 $_SESSION['username']。这个值将用作 Web 用户的客户端标识符。在实际应用程序中,将推荐使用不那么显而易见的标识符。例如,作为实现最终用户成功登录的应用程序身份验证的一部分,初始查找查询或 PL/SQL 函数可返回一个经过预先计算的晦涩的值来用作用户客户端标识符。这个值随后会存储在 PHP 会话信息中,供后续“实际”应用程序工作使用。晦涩难懂的值将使攻击者难以猜测到标识符值。此外,如果对使用标识符的 HTTP 请求的真实性存在质疑,还可迅速更改标识符。
应用程序页面 application.php 检查用户是否通过身份验证 — 只要用户名已设定,该应用程序就断定用户已经过身份验证。随后,代码设置客户端标识符,通过查询 PARTS 表显示库存列表:<?php // application.php require_once('./dbinfo.inc.php'); session_start(); // Check the user is logged in according to our application authentication if (!isset($_SESSION['username'])) { echo <<<EOD <h2>Unauthorized</h2> <p>You are not authenticated.<br> Valid usernames/passwords are "chris/tiger" and "alison/red"<p> <p><a href="login.php">Login Page</a><p> EOD; exit; } // Generate the application page $c = oci_pconnect(ORA_CON_UN, ORA_CON_PW, ORA_CON_DB); // Set the client identifier after every connection call // using a value unique for the web end user. oci_set_client_identifier($c, $_SESSION['username']); $username = htmlentities($_SESSION['username'], ENT_QUOTES); echo <<<EOD <body style="font-family: Arial, sans-serif;"> <h2>Parts Company</h2> <table border='1'> <caption><b>Inventory for $username </b></caption> EOD; $s = oci_parse($c, "select * from parts order by id"); oci_execute($s); while (($row = oci_fetch_array($s, OCI_ASSOC+OCI_RETURN_NULLS)) != false) { echo "<tr>\n"; foreach ($row as $item) { echo " <td>" . ($item!==null?htmlentities($item, ENT_QUOTES):" ") . "</td>\n"; } echo "</tr>\n"; } echo <<<EOD </table> <p><a href="logout.php">Logout</a></p> </body> EOD; ?>
作为 Chris 登录时,应用程序显示:
注销脚本 logout.php 清除 PHP 会话信息:
<?php // logout.php session_start(); unset($_SESSION['username']); echo <<<EOD <body style="font-family: Arial, sans-serif;"> <h2>Goodbye</h2> <p>You are logged out.<p> <p><a href="login.php">Login Page</a><p> </body> EOD; ?>
注销页面不会调用 dbms_session.clear_identifier 来清除该数据库连接的标识符:如果对应用程序中的 oci_set_client_identifier() 是否得到了一致的使用存在疑虑,则需要在使用数据库连接的文件中完成。
相同的应用程序代码将用于后续部分,无需任何修改。
总而言之,这个简单的应用程序旨在展示数据库用户和最终用户之间的关系,借此讨论客户端标识符。它并不是适合于生产使用的示例。该应用程序在每个 oci_pconnect() 连接调用之后立即使用 oci_set_client_identifier() 设置客户端标识符。此标识符唯一地标识了正在使用 Web 浏览器的最终用户。对于现有的实际应用程序,添加一个 oci_set_client_identifier() 调用,为每个 Web 用户使用一个唯一的标识符,是为了利用客户端识别而需要作出的唯一应用程序更改。
审计允许您:
Oracle 审计是强大、多面的。您可以审计一般活动,如所执行的 SQL 语句的类型。您可以审计细粒度的活动,例如特定值在何时发生、哪个 IP 地址发起了请求等。成功和失败的活动均可审计。审计线索可以存储在数据库内部或外部,适合于使用各种工具进行分析。
设置客户端标识符将允许审计与各自唯一的 Web 用户关联,而不仅仅是与验证了 PHP OCI8 oci_pconnect() 数据库调用的数据库表所有者关联。
auditon.sql 脚本是对 PARTS 表的查询进行审计的基本示例:
-- auditon.sql -- Turn on object auditing for the PARTS table connect system/welcome audit select on phpuser.parts by access;
在 SQL*Plus 中运行上述脚本。随后运行应用程序,作为“chris”或“alison”登录(其口令可在 setup.sql 中找到)。您甚至可以在应用程序之外在 SQL*Plus 中作为 SYSTEM 用户来查询表:
select * from phpuser.parts;
这将返回想要的配件列表。
要显示来自所有这些表访问的审计线索,SQL 脚本 auditreport.sql 将查询 DBA_AUDIT_TRAIL 视图,在数据库初始化参数 AUDIT_TRAIL 设置为 DB 时,此视图中包含审计数据。
-- auditreport.sql -- View the audit trail for the PARTS table connect system/welcome set pagesize 100 col app_username format a13 col username format a13 col extended_timestamp format a37 col action_name format a13 select auth.app_username, dat.username, extended_timestamp, action_name from dba_audit_trail dat left outer join php_sec_admin.php_authentication auth on auth.app_username = client_id where obj_name = 'PARTS' order by extended_timestamp;
运行报告将显示每位人员访问 PARTS 表的时间:
APP_USERNAME USERNAME EXTENDED_TIMESTAMP ACTION_NAME ------------- ------------- ------------------------------------- ------------- chris PHPUSER 16-AUG-10 12.25.42.846153 PM -07:00 SELECT alison PHPUSER 16-AUG-10 12.25.50.870773 PM -07:00 SELECT SYSTEM 16-AUG-10 12.25.58.660922 PM -07:00 SELECT
没有为 SYSTEM 用户显示 APP_USERNAME,因为在 SQL*Plus 会话中未设置客户端标识符。有些时候,识别客户端标识符未正确设置的数据库访问就是所需的审计目标。Oracle 的细粒度审计可用于审计类似于这样的特定事件,帮助监视可疑活动。在客户端标识符被虚拟专用数据库用于限制数据库访问,但不需要完整审计的情况下,这可能非常有用。
查看该示例之后,您可在 SQL*Plus 中使用 NOAUDIT 命令关闭审计:
-- auditoff.sql -- Turn off object auditing for the PARTS table connect system/welcome noaudit all on phpuser.parts;
在 Oracle 数据库安全性指南 11g 第 2 版 (11.2) 手册的通过审计验证安全性访问一章中可以找到有关审计的更多信息。
限制访问以避免误用敏感数据,这是所有应用程序的一项架构目标。Oracle PHP 应用程序可以利用客户端标识符通过手动编码或自动方法限制数据访问。手动方法是修改每一条 SQL 和 PL/SQL 语句,令其使用 SYS_CONTEXT(),该函数返回 PHP 连接的客户端标识符。例如,查询语句可以编写为只有在当前连接的标识符为“chris”时才从 PARTS 返回记录行:
select * from parts where sys_context('userenv', 'client_identifier') = 'chris';
在 Alison(或任何使用其他客户端标识符的用户)连接时,WHERE 子句的计算结果将为 false,因而不会返回任何行。此类逻辑编码麻烦,也容易出错,很难在所有位置得到一致的实现。对此,Oracle Database 企业版的虚拟专用数据库 (VPD) 技术可以提供帮助。它将自动为应用程序执行的每条语句添加一个 WHERE 谓词。
为了设置 VPD,需要创建一个返回限制性 WHERE 子句所需文本的 PL/SQL 函数。为了自动限制查询 select * from parts 返回的数据,该函数仅需返回以下字符串:
sys_context('userenv', 'client_identifier') = 'chris'
在启用了 VPD 来使用此类函数的情况下,Oracle 执行查询时将按照查询具有限制性的 WHERE 子句的方式执行,得到与上述手动实现相同的应用程序行为。从技术上来讲,Oracle 使用一个临时视图来强制使用 WHERE 子句并重写应用程序查询来使用该视图而非基表,如 VPD 文档所述。无论实现细节如何,Oracle 都会透明地处理授权,因而将实现一致的安全性,而程序员可专注于其他任务从而提高工作效率。切记,从数据库的角度来看,客户端标识符是“不安全的”,因为数据库必须依赖外部提供的信息来实施策略。这是在无状态 Web 架构中使用共享数据库连接和中间层身份验证的结果,它依赖于正确的应用程序代码。
对于 Parts 应用程序,SQL 脚本 vpdon.sql 将设置 VPD。首先,它会创建一个特定于应用程序的权限表。我已经决定,Chris 仅可查看电子用品,但 Alison 可以查看电子和水暖用品。VPD 策略函数 F_POLICY_PARTS 返回一个子查询,该子查询检查当前标识符是否具有对行的配件类别的访问权限。尽管向 F_POLICY_PARTS 传递了该策略将应用到的模式和表名称,但在本例中,该策略仅用于一个表,因而不会引用函数参数。在定义了策略函数后,使用 DBMS_RLS.ADD_POLICY 为 PARTS 表启用此函数。
-- vpdon.sql set echo on connect / as sysdba grant execute on sys.dbms_rls to php_sec_admin; connect php_sec_admin/welcome -- Application policy table drop table php_privs; create table php_privs (username varchar2(64), category varchar2(20)); -- Chris should only see electrical items. Alison can see -- electrical and plumbing items insert into php_privs values ('chris', 'electrical'); insert into php_privs values ('alison', 'electrical'); insert into php_privs values ('alison', 'plumbing'); commit; grant select on php_privs to phpuser; -- Policy function F_POLICY_PARTS returns a where clause to restrict access create or replace function f_policy_parts (schema in varchar2, tab in varchar2) return varchar2 as predicate varchar2(400); begin predicate := 'category in (select category from php_sec_admin.php_privs where username = sys_context(''userenv'', ''client_identifier''))'; return predicate; end; / show errors begin dbms_rls.add_policy ( object_schema => 'PHPUSER', object_name => 'PARTS', policy_name => 'ACCESS_CONTROL_PARTS', function_schema => 'PHP_SEC_ADMIN', policy_function => 'F_POLICY_PARTS', policy_type => DBMS_RLS.STATIC); end; /
应用程序中的策略函数可具有所需的任意复杂度。您自己的 Web 站点可以按照最适合的方式实现策略规则,这很可能与本例中所用的完全不同。请注意 POLICY_TYPE 参数。在这里,策略函数将返回一个简单的字符串,使函数在所有使用情况下都保持相同。这也就意味着,可将类型指定为 STATIC,允许函数缓存。客户端标识符不会被视为用户定义的应用程序上下文的一部分,因而,如果策略函数逻辑评估了标识符的值,那么您就需要将类型设置为 DYNAMIC。
以 Chris 的身份登录 Parts 应用程序,可以看到库存列表现在只显示出电子用品:
使用 Alison 的身份登录时,您仍可以看到一切内容:
Oracle 数据库有多种视图支持 VPD 管理。V$VPD_POLICY 视图就是其中之一,可用于找到应用于已执行 SQL 语句的策略。在调试策略函数返回的值时,这可能非常有用。
对于 VPD,一件非常有趣的事情是,作为 PARTS 表的所有者登录 SQL*Plus,然后查看其内容:
connect phpuser/welcome select * from parts;
由于策略函数 F_POLICY_PARTS 同样应用于表所有者,因而不会返回任何一行。如果不能有效地设定标识符,条件就永远无法满足。为了消除这项限制、简化对象的管理,Oracle 提供了一种 EXEMPT ACCESS POLICY 权限,用于使用户免于 VPD 策略。
VPD 的用途不仅仅在于个人权限控制。这种模型还可以扩展以允许“共享托管”。一个应用程序的基础架构可在从未获得授权,以查看来自其他任何组的数据的不同用户组之间共享。
在您完成 VPD 示例之后,即可删除策略:
-- vpdoff.sql set echo on connect php_sec_admin/welcome begin dbms_rls.drop_policy ( object_schema => 'PHPUSER', object_name => 'PARTS', policy_name => 'ACCESS_CONTROL_PARTS'); end; /
在 Oracle 数据库安全性指南 1g 第 2 版 (11.2) 手册的使用 Oracle 虚拟专用数据库控制数据访问一章中可以找到有关 VPD 的更多信息。
尽管许多调优项目都从 Oracle 数据库运行的自动性能诊断开始,或者是使用 AWR 快照手动分析整体系统性能,但并非在所有环境中都能完成这些任务。有些时候,在共享系统中,监视和分析一名 Web 用户的行为更加实际,可简化诊断 PHP 中性能问题的过程。在开发过程中,建议的应用程序补丁的行为可与系统中发生的其他活动隔离。客户端标识符允许通过 Oracle 的端到端应用程序跟踪进行重点监视,这是一项针对多层应用程序引入的特性。
为了收集用户数据库资源使用情况的数据库统计信息,数据库管理员可以在 SQL*Plus 中执行 DBMS_MONITOR.CLIENT_ID_STAT_ENABLE 过程:
connect system/welcome execute dbms_monitor.client_id_stat_enable(client_id => 'chris');
可以对任何选定操作和时段正常运行该应用程序。可通过多种方法访问统计信息,包括从 V$CLIENT_STATS 视图访问。在 Chris 查看了 Parts 应用程序库存一次之后,视图中可能包含:
STAT_NAME VALUE ----------------------------------- ---------- user calls 1 DB time 943 DB CPU 2000 parse count (total) 1 parse time elapsed 91 execute count 1 sql execute elapsed time 494 opened cursors cumulative 1 session logical reads 7 physical reads 0 physical writes 0 . . .
这个特殊示例表明解析并执行了单独一条 SQL 语句。标准 Oracle 手册和文章介绍解释了所有值,还说明了其他统计视图。
可以如下关闭统计并将其重置为零:
execute dbms_monitor.client_id_stat_disable(client_id => 'chris');
还可以为每名 Web 用户启动数据库跟踪以显示 SQL“解析计划”输出,以便分析所执行的语句。它还将显示语句的实际优化方式 — 不仅仅是您认为它们是如何运行的。数据库管理员可以通过 SQL*Plus 中的 DBMS_MONITOR.CLIENT_ID_TRACE_ENABLE 过程启用跟踪:
connect system/welcome execute dbms_monitor.client_id_trace_enable(client_id => 'chris', waits => true, binds => true);
随后,可正常运行该应用程序。完成分析过程之后,即可如下关闭跟踪:
execute dbms_monitor.client_id_trace_disable(client_id => 'chris');
要查看创建的跟踪文件,请在 SQL*Plus 中使用 SHOW PARAMETER 查找跟踪目录:
show parameter user_dump_dest
这会提供如下输出:
NAME TYPE VALUE ------------------------------------ ----------- ------------------------------ user_dump_dest string /home/oracle/app/diag/rdbms/or cl/orcl/trace
跟踪目录通常包含来自正常操作的许多跟踪文件。trcsess 工具可以整合 Chris 使用应用程序创建的任意跟踪文件。随后,整合的文件(或独立文件)可以使用 TKPROF 进行格式化。例如,作为 Oracle 软件所有者启动一个终端窗口,并运行以下命令:
cd /home/oracle/app/diag/rdbms/orcl/orcl/trace trcsess output=/tmp/all.trc clientid=chris *.trc tkprof /tmp/all.trc /tmp/tkprof.out explain=phpuser/welcome
这将查找目录中的所有跟踪文件,并聚合 Chris 创建的跟踪文件。如果您需要对文件子集(如特定一天的文件)运行 trcsess,可搜索文件顶端附近的客户端标识符,并将相关文件名传递给 trcsess。独立跟踪文件包含这样的一个部分:
*** 2010-08-16 15:29:12.481 *** SESSION ID:(143.943) 2010-08-16 15:29:12.481 *** CLIENT ID:(chris) 2010-08-16 15:29:12.481 *** SERVICE NAME:(orcl) 2010-08-16 15:29:12.481 *** MODULE NAME:(httpd@localhost (TNS V1-V3)) 2010-08-16 15:29:12.481 *** ACTION NAME:() 2010-08-16 15:29:12.481
Oracle 数据库还可使用给定后缀命名文件,例如“php”,以使其更易于识别。在连接之后,通过在 PHP 中执行 SQL 命令 ALTER SESSION SET TRACEFILE_IDENTIFIER = 'php' 完成此任务。随后,数据库服务器跟踪文件名应类似于 orcl_ora_9414_php.trc。将需要为每个 PHP 文件添加逻辑,以确定要使用的后缀以及在哪些条件下设置它。
tkprof.out 中 TKPROF 的输出包含已执行语句的分析。以下是在我的系统上文件分析结果的一部分:
SQL ID: af69s0fa3cjnp Plan Hash: 3769467330 select * from parts order by id call count cpu elapsed disk query current rows ------- ------ -------- ---------- ---------- ---------- ---------- ---------- Parse 5 0.00 0.16 0 8 0 0 Execute 33 0.00 0.00 0 0 0 0 Fetch 33 0.00 0.00 0 231 0 198 ------- ------ -------- ---------- ---------- ---------- ---------- ---------- total 71 0.01 0.17 0 239 0 198 Misses in library cache during parse: 1 Optimizer mode: ALL_ROWS Parsing user id: 1602 (PHPUSER) Rows Row Source Operation ------- --------------------------------------------------- 6 SORT ORDER BY (cr=7 pr=0 pw=0 time=5 us cost=4 size=222 card=6) 6 TABLE ACCESS FULL PARTS (cr=7 pr=0 pw=0 time=5 us cost=3 size=222 card=6) Rows Execution Plan ------- --------------------------------------------------- 0 SELECT STATEMENT MODE: ALL_ROWS 6 SORT (ORDER BY) 6 TABLE ACCESS (FULL) OF 'PARTS' (TABLE) Elapsed times include waiting on following events: Event waited on Times Max. Wait Total Waited ---------------------------------------- Waited ---------- ------------ SQL*Net message to client 33 0.00 0.00 SQL*Net message from client 32 2219.78 5720.55 cursor: mutex S 1 0.02 0.02 library cache lock 1 0.01 0.01 cursor: pin S wait on X 1 0.01 0.01 Disk file operations I/O 4 0.00 0.00 ********************************************************************************
理解 SQL 跟踪和 TKPROF 手册部分介绍了如何解释跟踪输出。
对于 GUI 爱好者,Enterprise Manager 11g(数据库控制)有多种方法检查应用程序对数据库的影响。例如,要查看特定客户端标识符的统计数据,可以在浏览器中启动 Enterprise Manager 控制台 http://localhost:5500/em/,导航到 Performance > Top Consumers > Top Clients。将 View 下拉菜单设置为“Clients with Aggregation Enabled”。单击 Add Client 并指定客户端标识符“chris”。随后,可以选择“chris”行,单击 Enable SQL Trace 按钮(这与 DBMS_MONITOR.CLIENT_ID_TRACE_ENABLE 相同)。聚合允许将每次运行合并在一起:
Enterprise Manager 的其他区域还可按照客户端标识符筛选,包括 Top Activity 报告。
小型 Parts 应用程序的单独一次运行可能不会形成 Enterprise Manager 监视阈值,可能在聚合阶段中不可见 — 我在一行中运行了多次,以捕捉屏幕快照。
您可能已经注意到 trcsess 和 Enterprise Manager 也允许数据按照 Action 和 Module 聚合。可以分别使用 oci_set_action() 和 oci_set_module_name() 函数在 PHP OCI8 中设置这些值,以指定执行 PHP 应用程序的哪些部分。监视和跟踪可以显示应用程序级的热点,可以轻松识别所执行的 SQL 语句。
Enterprise Manager 在跟踪性能瓶颈和跟踪开发中数据库速度减慢的原因时非常有用。它允许在大型系统中执行实时分析,而不会影响其他并发 Web 用户。有关跟踪的更多信息,请参见 Oracle 数据库性能调优指南 11g 第 2 版 (11.2) 中的使用应用程序跟踪工具。
对于允许多个应用程序用户通过单个数据库用户名连接到数据库的 PHP Web 应用程序,应该使用客户端标识符。标识符是开发人员选择的值,可以从大多数 Web 应用程序中已有的有关最终用户的会话信息中得出。客户端标识符的设置方法是直接在连接到数据库的 PHP 脚本中调用 oci_set_client_identifier() 函数。Oracle 数据使用标识符来进行审计、自动限制对敏感数据的访问、允许有重点地对资源使用情况进行监视和跟踪。Oracle PHP 应用程序应该使用客户端标识符,以便在应用程序生命周期内的任意点都可以利用这些 Oracle 数据库特性。
Christopher Jones 是 Oracle 的一名咨询技术人员。他是 PHP and Oracle 博客的作者。