PHP的oci8扩展详解

要连接到 Oracle,可以使用 PHP 的 oci_connect() 调用:

$c = oci_connect($username, $password, $dbname)
每个 oci_connect() 连接均存储在高速缓存中。当同一脚本中出现第二个 oci_connect() 时,将返回前一个高速缓存的连接。脚本完成时将清除该高速缓存。


Oci_new_connect() 提供了一个完全独立的连接。连接之间相互独立。这使您可以同时执行多个数据库事务:
$c = oci_new_connect($username, $password, $dbname)
持久连接在 PHP 脚本结束时不会自动关闭。它们仍保持打开状态,以便在其他脚本中重用:
$c = oci_pconnect($username, $password, $dbname)
当打开连接的开销很大时,使用持久连接很有好处。开销是否大取决于应用程序要求以及实现问题(如 web 服务器和数据库是否位于同一主机中)。


持久连接的缺点是始终使用 Oracle 资源,即使在无人访问应用程序或数据库的情况下也是如此。如果 Apache 创建了多个服务器进程,则每个进程将分别与数据库建立一组连接。可以使用 php.ini 参数调节持久连接的资源使用。
  • oci8.max_persistent:该参数限制高速缓存的持久连接数。达到该限制时,所有 oci_pconnect() 调用均被视为 oci_connect() 调用。将该参数设置为 -1(默认值)表示没有限制。

  • oci8.persistent_timeout:Apache 进程保持空闲持久连接的时间(以秒为单位)。每当 PHP 脚本完成时,无论脚本是否调用 oci8 函数,都将执行到期检查。将该参数设置为 -1(默认值)表示没有超时。如果连接已过期,oci_pconnect() 将创建一个新连接。

  • oci8.ping_interval:oci8 在执行 oci_pconnect() 过程中执行 ping 操作之前经过的秒数。如果该参数设置为 0,则 PHP 将在每次调用 oci_pconnect() 时对数据库执行 ping 操作。要禁用 ping,将该值设置为 -1。默认值为 60 秒。如果 ping 确定连接不可用,则将创建一个新连接。

良好的应用程序设计将透明地恢复大多数故障。尽管像 Oracle 这样的系统比较稳定,但任何应用程序均存在一些潜在的故障点,其中包括网络、硬件以及用户操作(如关闭数据库)。Oracle 本身可以经过配置来关闭空闲连接。DBA 可能已经使用 CREATE PROFILE IDLE_TIMEOUT 安装了用户配置文件。或者,Oracle Net 层可能已经使网络超时。


Oci_pconnect() 将始终检查 Oracle 客户端设置,以便根据上次从服务器收到的任何响应判断服务器是否可用。这是一个快速操作。另外设置 oci8.ping_interval 将对服务器执行物理 ping 操作,这将导致所谓的网络“往返”,并且将对可伸缩性带来不利影响。由于在连接检查之间以及当您实际使用连接时,数据库或某些数据仍有可能不可用,因此为了实现最高的可用性和可伸缩性,通常建议您不要使用 oci8.ping_interval,而是在应用程序代码中进行错误恢复。


当然,越来越易于使用的 ping 功能还是为许多小型应用程序带来了很多好处。


确保您了解应用程序连接的生存期。虽然应尽可能重用连接,但您也完全可以根据需要创建新连接以及关闭连接。每个连接都将占用一些 Oracle 内存,因此关闭空闲连接可以减少总负载。


连接字符串


最让人感到困惑的当数连接字符串。常见的 Oracle 错误“ORA-12514 TNS:listener does not currently know of service requested in connect descriptor”即使花费很长时间仍难以解决。


DB 名称可以为:
  1. 简单连接字符串

  2. 完整连接字符串

  3. tnsnames.ora 别名

简单连接字符串


简单连接字符串类似于 JDBC。它指定主机名、端口号和 Oracle 数据库服务名称:
//hostname:port/service_name
对于使用默认端口 1521 的 Oracle 数据库特别版,只需使用“//localhost/XE”。
$c = oci_connect('hr', 'hrpw', '//localhost/XE');
必须具有 Oracle 10 g 客户端库才能使用简单连接语法。ZCO 具有相应的 Oracle 库。


完整的硬编码连接字符串


完整 Oracle Net 连接字符串提供了足够的连接灵活性。
$db = MYDB2 = (DESCRIPTION =
(ADDRESS = (PROTOCOL = TCP)
(HOST = mymachine.mydomain)(PORT = 1521))
(CONNECT_DATA=
(SERVER = DEDICATED)
(SERVICE_NAME = MYDB.AU.ORACLE.COM)))';

$c = oci_connect($un, $pw, $db);
如果存在疑问,请复制其他 Oracle 工具和用户使用的连接字符串。


通过使用完整语法,可以实现像负载平衡这样的 Oracle Net 特性,并可以调整包大小。简单连接语法不具备这样的灵活性。


Tnsnames.ora 文件中的网络别名


可以将完整的连接字符串存储在一个名为 tnsnames.ora 的文件中,并通过别名在 PHP 中引用它
# tnsnames.ora
MYDB2 = (DESCRIPTION =
(ADDRESS = (PROTOCOL = TCP)
(HOST = mymachine.mydomain)(PORT = 1521))
(CONNECT_DATA=
(SERVER = DEDICATED)
(SERVICE_NAME = MYDB.AU.ORACLE.COM)))
在 PHP 中,可以使用以下代码进行连接:
$c = oci_connect($un, $pw, 'MYDB2');
PHP 需要能找到 tnsnames.ora 文件来解析别名“MYDB2”。这是一个常见问题。


在“ORACLE_HOME”样式的安装(例如,XE)中,默认的 tnsnames.ora 位于以下位置:
$ORACLE_HOME/network/admin/tnsnames.ora
/usr/lib/oracle/xe/app/oracle/product/10.2.0/server/network/admin/tnsnames.ora
无论是使用默认位置还是其他位置,Apache 必须能够在启动时找到该目录。通常情况下(包括在 ZCO 中),这通过设置 TNS_ADMIN 环境变量实现。


Oracle 环境变量


oci8 扩展始终需要查找 Oracle 库和消息文件。找不到正确的文件将导致 PHP 返回 Oracle“ORA-12705:Cannot access NLS data files or invalid environment specified”。如果存在多个 Oracle 安装,则可能发生冲突。ZCO 包含它自己的 Oracle 即时客户端本地副本,该副本包含 Oracle 库和消息,因此您不会看到该错误。


NLS_LANG(全球化)和 TNS_ADMIN(网络)变量通常是 ZCO 所必需的。


必须设置启动 Apache 的环境中的环境变量,以便 oci8 扩展在首次加载时可以访问这些值。在 PHP 脚本中不要使用 putenv() 设置环境变量。所有 oci8 函数的反映并不一致。它肯定不可移植。


ZCO 修改了 apachectl,添加了 LD_LIBRARY_PATH。(如果 Oracle 提供了以不同方式链接的即时客户端,则未来版本的 ZCO 可能不需要该操作)。这使得可以重用 ZCO GUI 控制台来启动 Apache。


您可能需要使用 ZCO 的 TNS_ADMIN 和 NLS_LANG 执行类似操作,或者如果您手动启动 Apache,则在调用脚本中设置环境:
#!/bin/sh

TNS_ADMIN=/usr/local/apache/conf
export TNS_ADMIN
echo Starting Apache 
#export > /tmp/envvars
/usr/local/apache/bin/apachectl start
该示例假设 /usr/local/apache/conf/tnsnames.ora 存在。TNS_ADMIN 指向包含 tnsnames.ora 文件的目录。


关闭连接


在每个脚本结束时,将自动关闭使用 oci_connect() 或 oci_new_connect() 打开的连接。您也可以调用以下代码来显式关闭连接:
oci_close($c);
将回滚任何未提交的数据。


如果长时间运行的脚本只用少量时间与数据库交互,则您可能要关闭连接以将资源释放给其他数据库用户使用。


oci_close() 无法关闭使用 oci_pconnect() 打开的连接。这与持久资源在其他 PHP 扩展中的工作方式类似。


Oci_close() 通过引用计数起作用。仅当完成所有连接引用时,才会实际关闭连接。在本示例中,$c1 和 $c2 是一个连接,但仅当脚本结束时,才会关闭数据库连接。
$c1 = oci_connect('hr', 'hrpw', '//localhost/XE');
$c2 = oci_connect('hr', 'hrpw', '//localhost/XE');

do_query($c1, 'select user from dual');
oci_close($c1);
do_query($c1, 'select user from dual');
do_query($c2, 'select user from dual');
oci_close($c2);
在重构 oci8 之前,oci_close() 函数是一个无操作函数。即使在需要的情况下,您也无法显式关闭连接。现在,这种情况发生了变化,但您可以在必要时通过在 php.ini 中设置 oci8.old_oci_close_semantics 来恢复为旧行为。将 oci8.old_oci_close_semantics 设置为 1 可以使 oci_close() 再次成为无操作函数。默认值 0 表示 oci_close() 将关闭连接。


执行语句


oci8 中的查询遵循 Oracle 中常用的模型:分析、执行和获取。类似 CREATE INSERT 这样的语句只需分析和执行。如今,分析实际上只是一个“准备”步骤,这是因为 Oracle 的实际分析操作会在执行阶段执行。


您可以像在字符串中使用“%s”打印格式指示符那样选择将局部变量“绑定”到语句中。这样做可以提高性能和安全性。


您也可以“定义”将结果存储到的位置。大多数用户让 oci8 fetch 函数执行该操作。


可能的步骤包括:
  1. 分析 - 准备一个要执行的语句

  2. 绑定 - 可以有选择地让您绑定数据值(例如,在 WHERE 子句中)以便提高性能和安全性

  3. 定义 - 一个可选步骤,使您可以指定哪些 PHP 变量将保存结果。(不常见?)

  4. 执行 - 数据库处理该命令并缓冲结果。

  5. 获取 - 从数据库中取回查询结果。Oci8 提供了一些可供选择的获取语句。

没有哪个 PHP 函数调用可以一次性执行以上所有操作,但在应用程序中创建一个这样的函数也不难,它还允许您添加自定义的错误处理要求。


查询


以下是 oci8 中的一个基本查询:
$stid = oci_parse($c, 'select * from locations');
oci_execute($stid, OCI_DEFAULT);
oci_fetch_all($stid, $res);
确保在必要时使用双引号:
$stid = oci_parse($c, "select * from locations where city = 'Sydney'");
oci8 包含一些 fetch 函数,PHP oci8 参考手册对其进行了详细说明。
  • oci_fetch_all():一次性获取所有结果

  • oci_fetch_array():以您选择的数组形式获取下一行

  • oci_fetch_assoc():以关联数组的形式获取下一行

  • oci_fetch_object():以对象形式获取新行

  • oci_fetch_row():以整数索引的数组形式获取下一行

  • oci_fetch():与 oci_result()(返回给定字段的结果)一起使用

需要重复调用获取单个行的函数:
$stid = oci_parse($c, "select city from locations");
oci_execute($stid, OCI_DEFAULT);
while ($res = oci_fetch_row($stid)) {
echo $res[0] ."<br>\n";
}
某些函数包含可更改其行为的可选参数,例如,oci_fetch_array() 提供了一些选项,用于指定是以关联数组形式还是以数字索引数组形式(或同时以关联数组和数字索引数组形式)返回结果。关联数组使用大写列名命名。


由于一些未知的历史原因,某些 PHP 4 fetch 函数在默认情况下不返回 NULL 数据。对于这些函数,请指定您需要返回 NULL,否则结果可能不包含您选择的所有列。


可以使用两个 php.ini 参数调整 PHP 的总体查询性能:
  • oci8.default_prefetch:当执行每个数据库获取操作时 Oracle 返回的记录数。默认值为 10。调整该设置可以显著提高返回大量行的查询的性能。它每次返回尽可能多的数据,从而最大限度地降低了数据库服务器的“往返”次数。Oracle 将数据高速缓冲在它的客户端缓冲区中,并只向 PHP 提供 PHP 本身请求的行。

  • oci8.statement_cache_size:支持 OCI 客户端语句高速缓存。默认值为 20 个语句。可以通过将该值设置为 0 来禁用高速缓存。客户端语句高速缓存意味着甚至不需要将语句文本传输给数据库,从而进一步减少了网络通信量和服务器负载。高速缓存是一个纯 Oracle 会话,因此当使用持久连接时,该特性通常很有用。

Insert/Update/Delete/Create/Drop


执行 CREATE INSERT 等语句只需要进行分析和执行:
$s = oci_parse($c1, "create table i1test (col1 number)");
$r = oci_execute($s, OCI_DEFAULT);

$s = oci_parse($c1, "insert into i1test values (1)");
$r = oci_execute($s, OCI_DEFAULT);
只有一次性的应用程序配置部分才应使用 CREATE TABLE 。某些用户认为应用程序需要创建临时表,而 Oracle 与其他数据库的限制并不相同。


在应用程序运行之前创建一次临时表。Tom Kyte 在 asktom.oracle.com 中介绍了全局临时表。


在 Oracle 数据库中,创建和删除表将自动提交所有未提交的数据。这种情况无法改变。


事务


就像任何其他关系应用程序一样,使用事务保护数据完整性对于 PHP 同样重要。但在极个别情况下,您需要提交所有数据,或根本不提交数据。


在前面的示例中,我们在执行代码中指定了 OCI_EXECUTE 标志。
$r = oci_execute($s, OCI_DEFAULT);
oci_execute() 的默认模式为 OCI_COMMIT_ON_SUCCESS(其名称显示了它的功能)。而不必要的提交会影响数据库性能,这是因为它将导致不必要的网络通信量以及导致数据库文件 I/O 的浪费。这就是为什么首选 OCI_DEFAULT 的原因。


oci_execute() 的 PHP 手册对此进行了简要介绍:


使用 OCI_DEFAULT 模式时,您将创建一个事务。当您关闭连接或当脚本结束(以最先结束的为准)时,事务将自动回滚。您需要显式调用 oci_commit() 提交事务,或调用 oci_rollback() 终止它。


在该示例中,如果不显式提交,则在 PHP 脚本结束时将回滚第二行。要准确插入这两行,就不应提交第一行,而是应在第二行之后提交 - 与该示例正好相反。
$s = oci_parse($c, "insert into i2test values ('row 1')");
$r = oci_execute($s);

$s = oci_parse($c, "insert into i2test values ('row 2')");
$r = oci_execute($s, OCI_DEFAULT);

错误处理


任何可靠的应用程序的错误处理均增加会复杂性并需要仔细的设计。期待着意外情况的出现。检查所有返回值。


要获取 Oracle 错误消息,必须将连接资源传递给 oci_error():
$c = oci_connect("hr", "hr", "//localhost/XE");
if (!$c) {
$e = oci_error();  // no resource passed
var_dump($e);
}

$stid = oci_parse($c, "select city from locations");
if (!$stid) {
$e = oci_error($c);   // connection resource passed
var_dump($e);
}

$rc = oci_execute($stid, OCI_DEFAULT);
if (!$rc) {
$e = oci_error($stid);  // statement resource passed
var_dump($e);
}

$rc = oci_fetch_all($stid, $results);
if (!$rc) {
$e = oci_error($stid);  // statement resource passed
var_dump($e);
}

绑定变量


绑定变量类似于“%s”打印格式指定符。通过它们可以使用不同的变量值重新执行语句,从而获得不同的结果。强烈建议您使用绑定。


它们可以提高数据库总吞吐量。Oracle 可以对语句重用任何高速缓存的执行计划,即使其他人最初执行了该计划。


此外,绑定变量也是一个可以阻止 SQL 注入安全攻击的重要方法。用户数据通常被视为数据而非 SQL 语句的一部分。
$stid = oci_parse($c,
"select last_name from employees where employee_id = :eidbv");
$myeid = 101;
oci_bind_by_name($stid, ":EIDBV", $myeid);
oci_execute($stid, OCI_DEFAULT);
oci_fetch_all($stid, $res);
echo "Last name is:". $res['LAST_NAME'][0] ."\n";

// No need to re-parse
$myeid = 102;
oci_execute($stid, OCI_DEFAULT);
oci_fetch_all($stid, $res);
echo "Last name is:". $res['LAST_NAME'][0] ."\n";
绑定数据在调用 oci_execute() 时应可以访问。在 sub 函数中使用局部变量可能会导致作用域问题。


除了向 Oracle 中传入数据的“IN”绑定以外,还存在返回值的“OUT”绑定。它们通常用于从 PL/SQL 过程和函数返回值。


oci_bind_by_name() 函数使用可选的大小和数据类型参数。


绑定后的 PHP 数字与字符串之间进行相互转换。这意味着在绑定变量中返回数字数据值时通常必须向 OCIBindByName() 提供 length 参数。该长度是将返回的位数。

在某种情况下,您可能决定不使用绑定变量。当语句包含绑定变量时,优化器不知道您最终要使用的值。如果数据很固定,则您可能要对值进行硬编码。但如果数据是由用户输入,确保对其进行处理。


很多旧文档在对 oci_bind_by_name() 的调用中使用“&”。不要这样做。由于最新的 PHP 按引用调用清除了该语法,因此不建议使用该语法。我还发现该语法会导致一些问题。


你可能感兴趣的:(oracle,oci8)