企業級應用常需 PHP 串接 Oracle Database。PHP 提供兩種主流介面:OCI8 擴展(原生、功能最全)與 PDO_OCI(統一 PDO 介面、可攜性佳)。本文涵蓋環境建置、連線參數、CRUD 操作、常見錯誤排查與最佳實務。
下載對應作業系統、架構的 Basic Package + SDK Package:
instantclient-basic-linux.x64-19.x.rpm, instantclient-sdk-linux.x64-19.x.rpminstantclient-basic-windows.x64-19.x.zip, instantclient-sdk-windows.x64-19.x.zip安裝後設定環境變數:
# Linux /etc/profile.d/oracle.sh
export ORACLE_HOME=/usr/lib/oracle/19/client64
export LD_LIBRARY_PATH=$ORACLE_HOME/lib:$LD_LIBRARY_PATH
export PATH=$ORACLE_HOME/bin:$PATH
# Windows 系統環境變數
ORACLE_HOME=C:\instantclient_19_x
PATH=%ORACLE_HOME%;%PATH%
Linux (編譯):
# 安裝開發套件
yum install php-devel php-pear gcc make
# 編譯 OCI8
pecl install oci8
# 提示輸入: instantclient,/usr/lib/oracle/19/client64/lib
# 編譯 PDO_OCI (較少用,建議優先 OCI8)
pecl install pdo_oci
# 提示輸入: instantclient,/usr/lib/oracle/19/client64/lib
Windows:php.ini 啟用預編譯 dll:
extension=oci8_12c ; PHP 7.x
extension=oci8 ; PHP 8.x
extension=pdo_oci
重啟 Web 伺服器,php -m | grep -i oci 確認載入。
| 格式 | 範例 | 適用場景 |
|---|---|---|
| Easy Connect | 192.168.1.100:1521/ORCL | 無需 tnsnames.ora,最簡單 |
| TNS 別名 | ORCL | 已配置 $ORACLE_HOME/network/admin/tnsnames.ora |
| 完整描述 | (DESCRIPTION=(ADDRESS=(PROTOCOL=TCP)(HOST=192.168.1.100)(PORT=1521))(CONNECT_DATA=(SERVICE_NAME=ORCL))) | 複雜網路、負載均衡、無 tnsnames |
<?php
// 連線
$conn = oci_connect('scott', 'tiger', '192.168.1.100:1521/ORCL', 'AL32UTF8');
if (!$conn) {
$e = oci_error();
die("連線失敗:" . $e['message']);
}
// 建立資料表 (示範)
$ddl = "CREATE TABLE php_test (id NUMBER GENERATED BY DEFAULT AS IDENTITY PRIMARY KEY, name VARCHAR2(50), created_at DATE DEFAULT SYSDATE)";
$stid = oci_parse($conn, $ddl);
oci_execute($stid);
echo "資料表建立成功<br>";
// 新增 (綁定變數)
$sql = "INSERT INTO php_test (name) VALUES (:name)";
$stid = oci_parse($conn, $sql);
$names = ['Alice', 'Bob', '中文測試'];
foreach ($names as $name) {
oci_bind_by_name($stid, ':name', $name);
oci_execute($stid, OCI_COMMIT_ON_SUCCESS);
}
echo "新增完成<br>";
// 查詢
$sql = "SELECT id, name, created_at FROM php_test ORDER BY id";
$stid = oci_parse($conn, $sql);
oci_execute($stid);
echo "<table border=1><tr><th>ID</th><th>Name</th><th>Created</th></tr>";
while ($row = oci_fetch_array($stid, OCI_ASSOC + OCI_RETURN_NULLS)) {
echo "<tr>";
foreach ($row as $item) {
echo "<td>" . ($item !== null ? htmlspecialchars($item) : " ") . "</td>";
}
echo "</tr>";
}
echo "</table>";
// 更新
$sql = "UPDATE php_test SET name = :name WHERE id = :id";
$stid = oci_parse($conn, $sql);
oci_bind_by_name($stid, ':name', 'Alice Updated');
oci_bind_by_name($stid, ':id', 1);
oci_execute($stid, OCI_COMMIT_ON_SUCCESS);
// 刪除
$sql = "DELETE FROM php_test WHERE id = :id";
$stid = oci_parse($conn, $sql);
oci_bind_by_name($stid, ':id', 2);
oci_execute($stid, OCI_COMMIT_ON_SUCCESS);
// 釋放資源
oci_free_statement($stid);
oci_close($conn);
?>
<?php
$dsn = 'oci:dbname=192.168.1.100:1521/ORCL;charset=AL32UTF8';
$user = 'scott';
$pass = 'tiger';
try {
$pdo = new PDO($dsn, $user, $pass, [
PDO::ATTR_ERRMODE => PDO::ERRMODE_EXCEPTION,
PDO::ATTR_DEFAULT_FETCH_MODE => PDO::FETCH_ASSOC,
PDO::ATTR_EMULATE_PREPARES => false,
PDO::ATTR_STRINGIFY_FETCHES => false, // 保持數字型別
]);
// 查詢
$stmt = $pdo->query("SELECT id, name FROM php_test");
foreach ($stmt as $row) {
echo "{$row['ID']} - {$row['NAME']}<br>"; // Oracle 欄位名預設大寫
}
// 新增 (Named placeholder)
$stmt = $pdo->prepare("INSERT INTO php_test (name) VALUES (:name)");
$stmt->execute([':name' => 'PDO User']);
} catch (PDOException $e) {
die("PDO 錯誤:" . $e->getMessage());
}
?>
// OCI8
oci_bind_by_name($stid, ':id', $id, -1, SQLT_INT); // 明確指定型別
// PDO
$stmt->bindValue(':id', $id, PDO::PARAM_INT);
防止 SQL 注入、提升解析重用效能、避免隱性型別轉換錯誤。
AL32UTF8(Oracle UTF-8 實作)。SELECT * FROM NLS_DATABASE_PARAMETERS WHERE PARAMETER='NLS_CHARACTERSET';mb_internal_encoding('UTF-8');。// OCI8
oci_execute($stid, OCI_NO_AUTO_COMMIT); // 手動控制
oci_commit($conn);
oci_rollback($conn);
// PDO
$pdo->beginTransaction();
$pdo->commit();
$pdo->rollBack();
// 寫入 CLOB
$lob = oci_new_descriptor($conn, OCI_D_LOB);
$stid = oci_parse($conn, "INSERT INTO docs (content) VALUES (EMPTY_CLOB()) RETURNING content INTO :lob");
oci_bind_by_name($stid, ':lob', $lob, -1, OCI_B_CLOB);
oci_execute($stid, OCI_NO_AUTO_COMMIT);
$lob->save($largeText);
$lob->free();
oci_commit($conn);
// 讀取 CLOB
$stid = oci_parse($conn, "SELECT content FROM docs WHERE id = :id");
oci_bind_by_name($stid, ':id', $id);
oci_execute($stid);
$row = oci_fetch_array($stid, OCI_ASSOC);
$content = $row['CONTENT']->load(); // LOB 物件需呼叫 load()
// OCI8 持久連線 (慎用,需配合 Oracle DRCP)
$conn = oci_pconnect('user', 'pass', 'db', 'AL32UTF8');
// PDO 持久連線
$pdo = new PDO($dsn, $user, $pass, [PDO::ATTR_PERSISTENT => true]);
高併發建議配合 Oracle Database Resident Connection Pooling (DRCP) 使用 oci_pconnect,否則易耗盡資料庫程序。
| 錯誤訊息 | 原因 | 解決 |
|---|---|---|
| ORA-12154: TNS:could not resolve the connect identifier | DSN 格式錯誤、tnsnames.ora 未配置 | 改用 Easy Connect 格式、檢查 tnsnames.ora 路徑權限 |
| ORA-12541: TNS:no listener | Listener 未啟動、IP/埠錯誤、防火牆阻擋 | tnsping 測試、檢查 1521 埠連通性 |
| ORA-28040: No matching authentication protocol | Oracle 12c+ 預設禁用舊版認證 | sqlnet.ora 加入 SQLNET.ALLOWED_LOGON_VERSION_SERVER=8 |
| OCIEnvNlsCreate failed | Instant Client 未正確安裝/環境變數缺失 | 檢查 LD_LIBRARY_PATH、ORACLE_HOME、重啟 php-fpm |
| Character set mismatch | 連線字元集與資料庫不符 | 統一指定 AL32UTF8 |