舊系統遷移、輕量級桌面資料庫、無 MySQL 環境時,使用 PHP PDO 透過 ODBC 驅動程式連接 Access (.mdb/.accdb)。
<?php
class DbHelper {
private PDO $conn;
public function __construct(string $mdbPath = null) {
$path = $mdbPath ?? __DIR__ . '/../../App_Data/sd#f#45G_!.mdb';
$realPath = realpath($path);
if (!$realPath || !is_file($realPath)) {
throw new RuntimeException("MDB 檔案不存在: $path");
}
$dsn = "odbc:Driver={Microsoft Access Driver (*.mdb)}; Dbq=$realPath;";
try {
$this->conn = new PDO($dsn);
$this->conn->setAttribute(PDO::ATTR_ERRMODE, PDO::ERRMODE_EXCEPTION);
$this->conn->setAttribute(PDO::ATTR_DEFAULT_FETCH_MODE, PDO::FETCH_ASSOC);
} catch (PDOException $e) {
throw new RuntimeException("Access 連線失敗: " . $e->getMessage());
}
}
/**
* 讀取查詢 (SELECT)
* @param string $sql SQL 語句,支援命名參數 :name
* @param array $params 參數陣列 ['name' => value]
* @return array 結果集陣列
*/
public function read(string $sql, array $params = []): array {
$stmt = $this->conn->prepare($sql);
foreach ($params as $key => $val) {
$stmt->bindValue(":$key", $val);
}
$stmt->execute();
return $stmt->fetchAll();
}
/**
* 寫入操作 (INSERT/UPDATE/DELETE)
* @return bool 執行成功與否
*/
public function execute(string $sql, array $params = []): bool {
$stmt = $this->conn->prepare($sql);
foreach ($params as $key => $val) {
$stmt->bindValue(":$key", $val);
}
return $stmt->execute();
}
/**
* 取得最後插入 ID (Access 需額外查詢)
*/
public function lastInsertId(): string {
$stmt = $this->conn->query("SELECT @@IDENTITY");
return $stmt->fetchColumn();
}
public function beginTransaction(): bool {
return $this->conn->beginTransaction();
}
public function commit(): bool {
return $this->conn->commit();
}
public function rollBack(): bool {
return $this->conn->rollBack();
}
}
// 使用範例
$db = new DbHelper();
// 讀取
$users = $db->read("SELECT * FROM Users WHERE Status = :status AND Age > :age", [
'status' => 1,
'age' => 18
]);
// 寫入
$ok = $db->execute("INSERT INTO Users (Name, Email, Status) VALUES (:name, :email, 1)", [
'name' => 'John',
'email' => 'john@example.com'
]);
// 交易
$db->beginTransaction();
try {
$db->execute("UPDATE Accounts SET Balance = Balance - 100 WHERE Id = 1");
$db->execute("UPDATE Accounts SET Balance = Balance + 100 WHERE Id = 2");
$db->commit();
} catch (Exception $e) {
$db->rollBack();
}
?>
| 項目 | 說明 |
|---|---|
| DSN 格式 | odbc:Driver={Microsoft Access Driver (*.mdb)}; Dbq=絕對路徑 |
| 錯誤模式 | PDO::ERRMODE_EXCEPTION 拋出異常,方便 try-catch |
| 預設取得模式 | PDO::FETCH_ASSOC 回傳關聯陣列 |
| 參數綁定 | bindValue 防 SQL 注入,自動處理型別 |
| 檔案路徑 | 必須使用 realpath() 解析絕對路徑,ODBC 不支援相對路徑 |
unixODBC + mdbtools,DSN 驅動名稱不同pdo_odbc、odbc 擴展| 問題 | 解決 |
|---|---|
| 找不到驅動程式 | 安裝 Microsoft Access Database Engine 2010/2016 Redistributable (32/64 位元需與 PHP 一致) |
| 權限被拒 | MDB 檔案所在目錄需 PHP 進程讀寫權限;.ldb 鎖檔需寫入權限 |
| 中文亂碼 | 確保 PHP、ODBC、Access 皆為 UTF-8/Unicode;必要時 iconv 轉碼 |
| 並發鎖定 | Access 檔案鎖機制簡單,高並發建議遷移至 SQLite/MySQL |
Access 適合單機、低並發、資料量小 (<100MB) 場景。Web 應用建議遷移至 SQLite (檔案型、無伺服器、支援並發 WAL 模式) 或 MySQL/PostgreSQL,PDO 程式碼幾乎無需修改即可切換。