前言:ODBC 通用資料庫連接標準
ODBC (Open Database Connectivity) 讓 PHP 以統一介面存取異構資料庫 (Access、SQL Server、DB2、Oracle、MySQL、PostgreSQL 等)。適用於遷移專案、舊系統整合、異構查詢。現代新專案建議使用原生驅動 (PDO_SQLSRV、PDO_OCI、PDO_PGSQL) 以獲得更好效能與功能。
環境準備 (Linux)
# Ubuntu 22.04
sudo apt-get update
sudo apt-get install -y unixodbc unixodbc-dev php-odbc
# Microsoft ODBC Driver for SQL Server (支援 SQL Server 2012+)
curl https://packages.microsoft.com/keys/microsoft.asc | apt-key add -
curl https://packages.microsoft.com/config/ubuntu/22.04/prod.list > /etc/apt/sources.list.d/mssql-release.list
sudo apt-get update
sudo ACCEPT_EULA=Y apt-get install -y msodbcsql18
# Access (mdb/accdb) - 需 MDB Tools
sudo apt-get install -y mdbtools
# 或使用 UCanAccess (Java JDBC) 配合 PHP-Java Bridge
DSN 配置方式
| 方式 | 適用場景 | 範例 |
| 系統 DSN (系統層) | 多 PHP 進程共用、GUI 管理 | /etc/odbc.ini、odbcinst.ini |
| 使用者 DSN | 單用戶、無 root 權限 | ~/.odbc.ini |
| 無 DSN (DSN-less) | 部署簡單、版本控管連線字串 | 直接在 PHP 程式碼建構連線字串 |
DSN-less 連線字串範例
<?php
// Microsoft Access (.mdb/.accdb)
$dsn = "Driver={Microsoft Access Driver (*.mdb, *.accdb)};Dbq=" . realpath('data.accdb') . ";";
// SQL Server (ODBC Driver 18)
$dsn = "Driver={ODBC Driver 18 for SQL Server};Server=192.168.1.100,1433;Database=mydb;Encrypt=yes;TrustServerCertificate=no;";
// PostgreSQL (psqlODBC)
$dsn = "Driver={PostgreSQL Unicode};Server=127.0.0.1;Port=5432;Database=mydb;";
// MySQL (MySQL ODBC)
$dsn = "Driver={MySQL ODBC 8.0 Unicode Driver};Server=127.0.0.1;Port=3306;Database=mydb;Charset=utf8mb4;";
PHP ODBC 基本操作
<?php
$dsn = "Driver={Microsoft Access Driver (*.mdb, *.accdb)};Dbq=" . realpath('data.mdb');
$conn = odbc_connect($dsn, '', '', SQL_CUR_USE_ODBC); // 游標類型
if (!$conn) die("Connection failed: " . odbc_errormsg());
// 1. 執行無結果集 SQL (INSERT/UPDATE/DELETE/DDL)
$sql = "INSERT INTO products (name, price) VALUES (?, ?)";
$stmt = odbc_prepare($conn, $sql);
$params = ['Widget', 19.99];
if (!odbc_execute($stmt, $params)) {
echo "Execute failed: " . odbc_errormsg($stmt);
}
// 2. 查詢結果集
$sql = "SELECT id, name, price FROM products WHERE price > ? ORDER BY price DESC";
$stmt = odbc_prepare($conn, $sql);
odbc_execute($stmt, [10.00]);
// 逐行處理 (記憶體友善)
while (odbc_fetch_row($stmt)) {
$id = odbc_result($stmt, 'id');
$name = odbc_result($stmt, 'name');
$price = odbc_result($stmt, 'price');
echo "$id | $name | $price\n";
}
// 3. 取得欄位中繼資料
$cols = odbc_num_fields($stmt);
for ($i = 1; $i <= $cols; $i++) {
echo "Column $i: " . odbc_field_name($stmt, $i) . " (" . odbc_field_type($stmt, $i) . ")\n";
}
odbc_free_result($stmt);
odbc_close($conn);
進階:預處理語句與參數綁定
<?php
$conn = odbc_connect($dsn, 'user', 'pass', SQL_CUR_USE_ODBC);
// odbc_prepare + odbc_execute 為預處理 (驅動支援度不一)
$stmt = odbc_prepare($conn, "SELECT * FROM orders WHERE customer_id = ? AND order_date >= ?");
$params = [12345, '2024-01-01'];
if (odbc_execute($stmt, $params)) {
// 處理結果
while (odbc_fetch_array($stmt)) { /* ... */ }
} else {
// 錯誤處理
$err = odbc_error($conn);
$msg = odbc_errormsg($conn);
throw new RuntimeException("ODBC Error [$err]: $msg");
}
編碼處理策略
- Access:舊版 .mdb 通常為系統預設編碼 (Big5/GBK/CP1252),需
iconv('BIG5', 'UTF-8', $val)。
- SQL Server:ODBC Driver 17+ 預設 UTF-8,資料庫欄位
NVARCHAR 存 Unicode,無需轉碼。
- 統一策略:資料庫層存 UTF-8,PHP 端統一
SET NAMES utf8mb4 (MySQL) 或依賴驅動自動轉碼。
效能優化與連線池
| 優化 | 說明 |
SQL_CUR_USE_ODBC | 強制使用 ODBC 游標管理器,解決部分驅動 odbc_num_rows 回傳 -1 的問題 |
| 持久連線 | odbc_pconnect() 重用連線,適用 CLI 長運行/高頻請求,注意進程分叉後共用連線問題 |
| 批次操作 | 關閉自動提交 odbc_autocommit($conn, false),批次執行後 odbc_commit() |
| 只選需要欄位 | 避免 SELECT *,減少網路傳輸與 ODBC 驅動轉換開銷 |
| 連線池 (PHP-FPM) | ODBC 無原生連線池,建議使用 ProxySQL 或 PgBouncer (PostgreSQL) 代理 |
常見錯誤對照
| 錯誤代碼/訊息 | 原因 | 解決 |
IM002 Data source name not found | 驅動未安裝、DSN 名稱錯誤、32/64 位不匹配 | 確認 odbcinst -q -d 列出驅動、PHP 與驅動位數一致 |
HY000 General error | SQL 語法錯誤、權限不足、資料類型不符 | 啟用 odbc_errormsg() 取得詳細訊息 |
01004 String data right truncated | 欄位長度不足 | 調整資料庫欄位長度或截斷資料 |
08S01 Communication link failure | 網路中斷、防火牆、Server 端逾時 | 檢查網路、調整 ConnectionTimeout、啟用重連邏輯 |
完整封裝 Library (PDO 風格介面、自動重連、查詢建構器) 見 GitHub。