Oracle PHPトラブルシューティングFAQ

Christopher Jones
2004年1月

OracleとPHPのトラブルシューティングのヒント。 Oracle/PHPの一般的なFAQについては、 Frank Naudによる「Oracle/PHP FAQ」を参照してください。

トピック


Oracleが インストールされていない、または見つからない場合

PHPでOracleのサポートが有効になっているが、 Oracleクライアント・ライブラリが見つからない場合、Apacheを起動しようとしたときにエラーを受け取ります。 たとえばWindowsで、php.iniにextension=php_oci8.dllが指定されているが、Oracleホームが見つからない場合、 「The dynamic link library OCI.dll could not be found in the specified path ....」というアラートが表示されます。

Apacheを起動(次のトピックを参照)する前に Oracle環境変数が正しく設定されていることを確認してください。Rob ClevengerによるOTNの記事、Installing Oracle, PHP, and Apache on Windows 2000/XPも参照してください。

Linuxユーザーは、libclntsh.soをロー ドできないというApacheエラーを受け取る可能性がありますが、これによりも前の時点、PHPのコンパイル時にこの問題が通知される可能性が高いで す。 コンパイラは、「Cannot find file "ocidfn.h"」または「annot find file "oci.h"」というエラーを表示し、失敗します。

PHPをコンパイルするOSユーザーがOracleディ レクトリを読み込み可能であるかを確認してください。 Oracleをインストールしたが、Oracleヘッダー・ファイルがない場合、OracleのClientインストールを実行してください。 Oracle9i Release 2の場合、インストーラを実行し、「クライアント」オプションを選択、Oracle Database 10g Client Releaseの場合、インストーラを実行し、「Administrator」オプションを選択します。 OTN の記事、「Installing Oracle, PHP, and Apache on Linux」も参照してください。


Apache起 動前のシェルまたは環境に対するすべてのOracle環境変数の設定

PHPとOracleが安定して対話できるようにするた め、Apacheを起動する前にすべてのOracle環境変数を設定します。 通常、PHPスクリプトまたはhttpd.conf ファイルの変数を設定するだけではうまくいきません。 環境に関する一般的な混乱のために、数多くのメーリング・リストやフォーラムの投稿が存在します。 WindowsとLinuxでは、動作も異なります。

OTNの記事、「Installing Oracle, PHP, and Apache on Linux」には、環境を設定しApacheを起動するためのstart_apache というシェル・スクリプトの例が示されています。

     #!/bin/sh
ORACLE_HOME=/u01/app/oracle/product/9.2

ORACLE_SID=orcl
export ORACLE_HOME ORACLE_SID
echo "Oracle Home: $ORACLE_HOME"
echo "Oracle SID: $ORACLE_SID"
echo Starting Apache
./apachectl start

環境がPHPコールにどのような影響を与えるかを確認す るために、いくつかのテストを実行しました。

     $mycon = OCILogon("myusername", "mypassword", "MYDB");

RedHat Linux AS 2.1、Apache 1.3、およびPHP 4.3.3を使用しました。

  1. apachectl startの前にORACLE_HOMEを設定せず、PHPスクリプトにputenv('ORACLE_HOME=/usr/oracle/MYDB') を含めなかった場合、次のメッセージを受け取りました。

       Warning: ocilogon(): _oci_open_server: Error while trying to retrieve text for error ORA-12154

    これは、接続に失敗し、メッセージ・ファイル (Oracleホーム・ディレクトリの下に置かれる)が見つからなかったことを示しています。

  2. PHPスクリプトにputenv ('ORACLE_HOME=/usr/oracle/MYDB')を含めたが、ORACLE_HOMEを設定しなかった場合、次のメッセージを受け取り ました。

       Warning: ocilogon(): _oci_open_server: ORA-12154: TNS:could not resolve service name

    依然として接続は失敗しましたが、エラーの発生 後、メッセージ・ファイルからメッセージ・テキストを読むことができました。

  3. apachectl startの前にputenv()なしでORACLE_HOMEを設定し、接続に成功しました。 これは、推奨設定です。

  4. apachectl startの前に、無効なORACLE_HOMEディレクトリを使用したputenv()とともにORACLE_HOMEを正しく設定し、接続に成功しま した。 Windowsでもこれを試しました。 このときは、接続に失敗し、最初のテストと同じメッセージが表示されました。

PHP putenv()コールをApache httpd.confdディレクティブsetenv ORACLE_HOME /usr/oracle/MYDBに置き換えた場合も、同様の結果が得られました。

PHPスクリプトに、いくつかの変数を設定できます。 Apacheを起動する前にORACLE_HOMEを正しく設定した後、次のようにデフォルト接続を変更し、MYDBに接続しました。

    putenv("TWO_TASK=MYDB");
$mycon = OCILogon("myusername", "mypassword");

環境変数TNS_ADMIN、 NLS_DATE_FORMATも、この方法で設定することができます(その他の変数も設定できる可能性があります)。


データベースへ の接続

接続先のデータベースを識別するために、ユーザーが選択 したネット・サービス名がしばしば使用されます。 デフォルトでは環境変数ORACLE_SIDから読み込まれますが、接続コール内で明示的に指定することもできます。 ネット・サービス名MYDBは、OracleコマンドラインSQL*Plusユーティリティで次のように使用できます。

     sqlplus myusername/mypassword@MYDB

または、PHPで次のように使用されます。

     $mycon = OCILogon("myusername", "mypassword", "MYDB");

通常、ネット・サービス名は、tnsnames.ora ファイルのエントリによって、実際のデータベースにマップされます。

     MYDB =
(DESCRIPTION =
(ADDRESS_LIST =
(ADDRESS = (PROTOCOL = TCP)(HOST = mymachine.mydomain)(PORT = 1535))

)
(CONNECT_DATA =
(SERVER = DEDICATED)
(SERVICE_NAME = MYDB.mydomain)
)
)

デフォルトでは、ファイル$ ORACLE_HOME/network/admin/tnsnames.oraが使用されます。 一部のオペレーティング・システムでは、デフォルトが存在しないと、他の場所がチェックされます。

OCILogon()で使用されるネット・サービス名が tnsnames.oraで見つからない場合、または、PHPによりtnsnames.oraが見つからない場合、ログイン時にエラーを受け取る可能性が あります。

 Warning: ocilogon(): _oci_open_server: ORA-12154: TNS:could not resolve service name

Apache Webサーバーを起動する前に、環境変数ORACLE_HOMEが正しく設定されていることを確認し、前のトピックを参照してください。

tnsnames.oraがデフォルト以外の場所にある 場合、それが置かれているディレクトリに環境変数TNS_ADMINを設定することができます。 たとえば、/tmp/tnsnames.oraを使用する場合、start_apacheに次の行を追加します(前のトピックを参照してください)。

     TNS_ADMIN=/tmp
export TNS_ADMIN

別の方法として、OCILogon()コールに接続文字 列全体を使用することもできます。

      $db = "(DESCRIPTION=(ADDRESS_LIST=(ADDRESS=(PROTOCOL=TCP)
(HOST = mymachine.mydomain)(PORT=1535)))
(CONNECT_DATA=(SERVER=DEDICATED)
(SERVICE_NAME=MYDB.mydomain)))";

$mycon = OCILogon("myusername", "mypassword", $db);
...

また、$ ORACLE_HOME/network/admin/sqlnet.oraがtnsnames.oraと同期しておらず、ドメイン名がエイリアスに暗示 的に追加されている場合、ORA-12154エラーが発生する可能性があります。 OCILogon()コールの非修飾のネット・サービス名に対しては、sqlnet.oraのNAMES.DEFAULT_DOMAINの値が追加されま す。 たとえば、sqlnet.oraが次のような場合、

     NAMES.DEFAULT_DOMAIN = au.oracle.com

OCILogon("myusername", "mypassword", "mydb")により、Oracleはtnsnames.oraでエイリアスMYDB.AU.ORACLE.COM = ...を検索します。一番手早い解決策は、tnsnames.oraエントリを次のように変更することです。

        MYDB.AU.ORACLE.COM =
(DESCRIPTION =
(ADDRESS_LIST =
(ADDRESS = (PROTOCOL = TCP)(HOST = mymachine.mydomain)(PORT = 1535))
)
(CONNECT_DATA =
(SERVER = DEDICATED)
(SERVICE_NAME = MYDB.mydomain)
)
)

SQL文のエ コーおよび適切に構成されていることの確認

不適切な文の実行によって、結果が得られなかったり、間 違った結果がもたらされることがあります。 開発段階で、PHPからの個々のSQL文全体をエコーし、それが適切に構成され、すべての変数が正しく展開されているかを確認します。 引用符のエラーやPHPの文字列の変数構文の誤解が、不適切な文の実行の原因となる可能性があります。

PHPでSQL文を実行する前に、SQL*Plusで SQL文をテストすることは、正確さの確認にも役立ちます。

SQL*Plusのようなツールに文を入力する場合、文 が完全であり、実行可能であることをツールに伝えるために通常セミコロンが使用されます。 ただし、セミコロンは文の一部として捉えられず、データベースに送信されません。 PHPでは、セミコロンをSQL文に追加しないでください。これを追加した場合、Oracleエラーが発生します。 次の例は、有効な問合せです。

        $sql = "SELECT * FROM EMP_DETAILS_VIEW";

Oracleの組込みスクリプト言語PL/SQLの構文 はSQLと異なっており、最後にセミコロンが必要になります。

        $plsql = "BEGIN DBMS_OUTPUT.PUT_LINE('hi'); END;";


データベース関 数コールからのリターン・コードのテストの必要性

OCI関数からのすべてのリターン値をテストすることに より、隠れた問題や誤った結果を未然に防ぐことができます。

基本的なテストの場合、php.iniのerror_reportingディ レクティブをE_ALLに設定します。 また、display_errorsを onに設定するか、エラー・ログ・ファイルを設定します(忘れずに確認してください)。 これにより、明示的にOracle関数コールの前に"@"を 付けないかぎり、Oracleエラーが発生した場合にそれが表示されます。


引用符を含 む文字列の挿入

一重引用符を含む文字列を挿入する場合、いくつかの方法 で処理できます。

  • バインド変数を使用します。 これにより、SQLの挿入というセキュリティ上の問題からも保護できます。

            $name = "O'Reilly";
    $stmt = 'INSERT INTO CUSTOMERS (NAME) VALUES (:nm)';
    $stid = OCIParse($mycon, $stmt);
    OCIBindByName($stid, ':nm', $name, -1);
    OCIExecute($stid);
  • すべての一重引用符を二重にします。

            $name = "O'Reilly";
    $name = str_replace("'", "''", $name);
    $stmt = "INSERT INTO CUSTOMERS (NAME) VALUES ('".$name."')";

  • php.iniのmagic_quotes_sybaseを onにし、addslashes()を 使用します。

            $name = addSlashes("O'Reilly");
    $stmt = "INSERT INTO CUSTOMERS (NAME) VALUES ('".$name."')";

    これは、移植性の理由により推奨されません。


PHPでの Oracleバインド変数の使用

バインドに伴う問題は、OCIBindByName() の実行時だけではなく、OCIExecute()が呼び出された時点でデータ値にアクセス可能であることが必要となるためしばしば発生します。

ラッパー関数またはメソッド内で OCIBindByName()が呼び出され、OCIBindByName()に渡されるPHP変数がラッパー関数にとってローカルな場合、問題となる可 能性があります。 OCIExecute()が後で呼び出されたときに、変数がスコープ内にある必要があります。 変数がスコープ内にない場合、「OCIStmtExecute: ORA-01460: unimplemented or unreasonable conversion requested」などのOracleエラーが発生するか、または、あたかもOUT変数に値が設定されていないかのように表示される可能性があります。

次のサンプルは、これの1つのバリエーションです。 変数$valは、foreachコマンドにとってローカルです。 このコードはレコードを返しません。

     <?php

$qs = 'SELECT * FROM DEPARTMENTS WHERE DEPARTMENT_NAME = :dname AND LOCATION_ID = :loc';

$dn = 'IT Support';
$lc = '1700';
$ba = array(':dname' => $dn, ':loc' => $lc);

$conn = OCILogon('myusername', 'mypassword', 'mydb');
$stmt = OCIParse($conn, $qs);

foreach ($ba as $key => $val)
{
OCIBindByName($stmt, $key, $val, -1);
}

OCIExecute($stmt);

while ($succ = OCIFetchInto($stmt, $o)) {
foreach ($o as $mv) {
echo $mv." ";
}
echo "<br>\n";
}

?>

OCIBindByName()コールを変更することに よって、問題が解決されます。

     ...
foreach ($ba as $key => $val)
{
OCIBindByName($stmt, $key, $ba[$key], -1);
}
...

LOBSのアップ ロード

BLOBをアップロードするためのサンプル・コードは、OCINewDescriptor()手 動についてのマニュアル項目で与えられます。 次の例は、CLOBをロードする更新の例です。

HTMLスクリプトのサイズ制限に注意します。

        <input type="hidden" name="MAX_FILE_SIZE" value="3000">

または、httpd.confの LimitRequestBodyディレクティブのサイズ制限に注意します(PHP Bug 22138を参照)。

CLOBにアップロードする例を次に示します。

     <?php

//
// Sample form to upload and insert data into an ORACLE CLOB column

// using PHP's Oracle 8 API.
//
// Based on http://www.php.net/manual/en/function.ocinewdescriptor.php

// modified to work on CLOBs and use register_globals = Off.
//
// Before running this script, execute these statements in SQL*Plus:

// drop table myclobtab;
// create table myclobtab (c1 number, c2 clob);
//
// Tested with PHP 4.3.3 against Oracle 9.2
//

if (!isset($_FILES['lob_upload'])) {
?>

<form action="<?php echo $_SERVER['PHP_SELF'];?>
" method="POST" enctype="multipart/form-data">
Upload file: <input type="file" name="lob_upload">

<input type="submit" value="Upload">
</form>

<?php
}
else {

$myid = 1; // should really be a unique id e.g. a sequence number


$conn = OCILogon('myusername', 'mypassword', 'mydb');

// Delete any existing CLOB so the query at the bottom
// displays the new data

$query = 'DELETE FROM MYCLOBTAB';
$stmt = OCIParse ($conn, $query);
OCIExecute($stmt, OCI_COMMIT_ON_SUCCESS);
OCIFreeStatement($stmt);

// Insert the CLOB from PHP's tempory upload area

$lob = OCINewDescriptor($conn, OCI_D_LOB);
$stmt = OCIParse($conn, 'INSERT INTO MYCLOBTAB (C1, C2) VALUES('.

$myid . ', EMPTY_CLOB()) RETURNING C2 INTO :C2');

OCIBindByName($stmt, ':C2', $lob, -1, OCI_B_CLOB);
OCIExecute($stmt, OCI_DEFAULT);

// The function $lob->savefile(...) reads from the uploaded file.
// If the data was already in a PHP variable $myv, the
// $lob->save($myv) function could be used instead.
if ($lob->savefile($_FILES['lob_upload']['tmp_name'])) {
OCICommit($conn);
echo "Clob successfully uploaded\n";
}
else {
echo "Could not upload Clob\n";
}
$lob->free();
OCIFreeStatement($stmt);

// Now query the uploaded CLOB and display it

$query = 'SELECT C2 FROM MYCLOBTAB WHERE C1 = '.$myid;

$stmt = OCIParse ($conn, $query);
OCIExecute($stmt, OCI_DEFAULT);
OCIFetchInto($stmt, $arr, OCI_ASSOC);
$result = $arr['C2']->load();

echo '<pre>';
echo $result;
echo '</pre>';

OCIFreeStatement($stmt);

OCILogoff($conn);
}
?>


PHPでの配 列フェッチ

PHPには配列のフェッチ機能がありませんが、OCISetPrefetch()と ともにプリフェッチ行カウントを設定することにより同様の結果が得られ、パフォーマンスを大幅に高めることができます。 配列サイズのかわりにプリフェッチ行カウントを設定すると、Oracleにより自動的にキャッシュが実行されるという利点があります。 このデータ構造では、1回で1行のみが処理されるため、コードが簡素化されます。

プリフェッチ行カウントを100に設定した例を次に示し ます。

     <?php

$conn = OCILogon('myusername', 'mypassword', 'mydb');

$query = 'SELECT * FROM EMP_DETAILS_VIEW';

$stid = OCIParse($conn, $query);
OCIExecute($stid);
OCISetPrefetch($stid, 100);
while ($succ = OCIFetchInto($stid, $row)) {
foreach ($row as $item) {
echo $item." ";
}
echo "<br>\n";
}

OCILogoff($conn);

?>

PEAR DBのOracleエラー・メッセージ

PEAR DBインタフェース は、異なるデータベース・ブランドで同じ構文を使用するデータベース抽象レイヤーです。 標準PEAR DBエラー関数$db->getMessage()は、PEARエラーの簡単な説明を返します。 たとえば、なんらかの理由で接続が失敗した場合、メッセージは常に次のようになります。

     DB Error: connect failed

次のとおりに、正確なOracleエラー番号とメッセー ジを取得できます。

      $db->getDebugInfo()

これには、次のようにOracleエラーと文全体が含ま れます。

     [nativecode=ORA-01017: invalid username/password; logon denied ] ** oci8://myusername:wrongpassword@mydb

ラッパー関数を使用して、Oracleメッセージ・テキ ストをこの文字列から抽出できます。 例を次に示します。

     require_once('DB.php');

$db = DB::connect("oci8://myusername:mypassword@mydb");
if (DB::iserror($db)) {
PrintPEARDBError($db);
die();
}

.
.
.

// Display PEAR DB error
function PrintPEARDBError($e)
{
if (is_object($e)) {
$s = preg_match('/.*\[nativecode=(.*)/', $e->getDebugInfo(), $r);
$etxt = $s ? $r[1] : $e->getDebugInfo();
}
else {
$etxt = "Unknown Error";
}
echo "<p><b>Error</b>:</p>\n<pre>
".htmlspecialchars($etxt)."</pre>\n";
}

PrintPEARDBError()からの出力を次に 示します。

        Error:

     ORA-01017: invalid username/password; logon denied


OCIスレッ ド・セーフ

PHPライブラリは、PHP 4.3.5のバグフィックスで、OCIスレッド・セーフになりました。 実際には、クラッシュを含むランダムな動作を伴う問題がロードされたサーバのみに発生したかのように見えます。 修正は、最新のスナップショットで利用できます。 PHP bug 26558PHP Bug 26393を参照してください。


AS SYSDBAまたはAS SYSOPERを使用した接続

PHPでは、AS SYSDBAまたはAS SYSOPERで接続することができません。

PHPのoci8.cにおけるOracleの OCISessionBegin()関数のコールは、常にOCI_DEFAULTを渡します。 権限付きの接続を可能にするには、これをOCI_SYSDBAまたはOCI_SYSOPERに変更する必要があります。 しかし、この簡単なソリューションは、セキュリティ・ホールを開く可能性があります。「Re: suggestion about php ocilogon() oracle OCI FUNCTION」を参照してください。


PHPのNCHAR およびNCLOBサポート

PHPではNCHARまたはNCLOBはサポートされて いません。

oci8.cの現在のPHP実装では、Oracleの OCIを呼び出すとき、キャラクタ・セット形式に常にSQLCS_IMPLICITを使用します。 NCHARまたはNLCOBをサポートするには、キャラクタ・セット形式がSQLCS_NCHARである必要があります。PHPコードで他のデータ・ハン ドリングの変更も必要になる可能性があります。

 

 


Oracle Corporation
World Headquarters
500 Oracle Parkway
Redwood Shores, CA 94065
U.S.A.

海外からのお問合せ窓口:
+1.650.506.7000
Fax +1.650.506.7200
http://www.oracle.com/

Copyright (C) Oracle Corporation 2004
All Rights Reserved

このドキュメントはあくまで参考資料であり、掲載されている情報は予告なしに変更されることがあります。
内容の誤りがある場合は、オラクル社までお知らせください。
オラクル社は、このドキュメントに関する保証を行うものでなく、また、記載の内容に対して責任を負いません。

Oracleはオラクル社の登録商標です。
Oracle Corporation発行の「Oracle PHP Troubleshooting FAQ」の翻訳版です。

他のすべての企業名および製品名は、識別のためのにみ掲載されており、それぞれの所有者の商標です。