PDO (PHP Data Objects) 提供統一介面存取多種資料庫,是現代 PHP 專案的標準資料層。本文涵蓋 PDO 連接 MySQL 與 SQL Server (PDO_SQLSRV) 的完整設定、查詢執行、預處理、錯誤處理、進階封裝與最佳實踐。
<?php
$dsn = 'mysql:host=localhost;dbname=test;charset=utf8mb4';
$user = 'root';
$pass = 'password';
$options = [
PDO::ATTR_ERRMODE => PDO::ERRMODE_EXCEPTION,
PDO::ATTR_DEFAULT_FETCH_MODE => PDO::FETCH_ASSOC,
PDO::ATTR_EMULATE_PREPARES => false, // 使用真實預處理
];
try {
$pdo = new PDO($dsn, $user, $pass, $options);
} catch (PDOException $e) {
exit('MySQL 連線失敗:' . $e->getMessage());
}
// 簡單查詢
foreach ($pdo->query('SELECT * FROM `users`') as $row) {
print_r($row);
}
?>
<?php
// 需安裝 Microsoft ODBC Driver 17/18 + php_pdo_sqlsrv.dll
$dsn = 'sqlsrv:Server=127.0.0.1;Database=master;ConnectionPooling=1';
$user = 'sa';
$pass = 'password';
$options = [
PDO::ATTR_ERRMODE => PDO::ERRMODE_EXCEPTION,
PDO::ATTR_DEFAULT_FETCH_MODE => PDO::FETCH_ASSOC,
];
try {
$pdo = new PDO($dsn, $user, $pass, $options);
echo 'SQL Server 版本:' . $pdo->query('SELECT @@VERSION')->fetchColumn();
} catch (PDOException $e) {
exit('SQL Server 連線失敗:' . $e->getMessage());
}
?>
<?php
class Database extends PDO {
public function __construct(array $config) {
$dsn = $config['driver'] . ':host=' . $config['host'] . ';dbname=' . $config['dbname'];
if (!empty($config['charset'])) $dsn .= ';charset=' . $config['charset'];
parent::__construct($dsn, $config['user'], $config['pass'], [
PDO::ATTR_ERRMODE => PDO::ERRMODE_EXCEPTION,
PDO::ATTR_DEFAULT_FETCH_MODE => PDO::FETCH_ASSOC,
PDO::ATTR_EMULATE_PREPARES => false,
]);
}
// 安全查詢:自動記錄慢查詢、參數綁定
public function query($sql, array $params = []) {
$stmt = $this->prepare($sql);
$stmt->execute($params);
return $stmt;
}
// 單筆查詢
public function fetchOne($sql, array $params = []) {
return $this->query($sql, $params)->fetch();
}
// 全部查詢
public function fetchAll($sql, array $params = []) {
return $this->query($sql, $params)->fetchAll();
}
// 交易執行器
public function transaction(callable $callback) {
$this->beginTransaction();
try {
$result = $callback($this);
$this->commit();
return $result;
} catch (\Throwable $e) {
$this->rollBack();
throw $e;
}
}
}
// 使用範例
$config = [
'driver' => 'mysql',
'host' => 'localhost',
'dbname' => 'test',
'user' => 'root',
'pass' => 'password',
'charset'=> 'utf8mb4',
];
$db = new Database($config);
// 參數化查詢
$users = $db->fetchAll('SELECT * FROM `users` WHERE `status` = ? AND `role` = ?', [1, 'admin']);
// 交易
$db->transaction(function($db) {
$db->query('UPDATE `accounts` SET `balance` = `balance` - ? WHERE `id` = ?', [500, 1]);
$db->query('UPDATE `accounts` SET `balance` = `balance` + ? WHERE `id` = ?', [500, 2]);
$db->query('INSERT INTO `transfers` (`from`,`to`,`amount`) VALUES (?,?,?)', [1,2,500]);
});
?>
| 方法 | 回傳 | 適用場景 | 取得影響筆數 |
|---|---|---|---|
PDO::exec() | int | 無結果集的 INSERT/UPDATE/DELETE | 直接回傳 |
PDO::query() | PDOStatement | SELECT、SHOW 等有結果集 | 不適用 |
PDOStatement::execute() | bool | 預處理語句執行 | $stmt->rowCount() |
<?php
$pdo->setAttribute(PDO::ATTR_ERRMODE, PDO::ERRMODE_EXCEPTION); // 推薦:拋出例外
try {
$stmt = $pdo->prepare('SELECT * FROM `table` WHERE `id` = ?');
$stmt->execute([$id]);
$row = $stmt->fetch();
} catch (PDOException $e) {
// $e->getCode() => SQLSTATE 錯誤代碼 (如 23000 完整性約束違反)
// $e->getMessage() => 完整錯誤訊息
// $e->errorInfo => array(SQLSTATE, Driver Code, Driver Message)
error_log('DB Error: ' . $e->getMessage());
// 回傳友善錯誤給前端
http_response_code(500);
echo json_encode(['error' => '資料庫操作失敗']);
}
?>
| 現象 | 原因 | 解決 |
|---|---|---|
| 預處理不生效、仍被注入 | ATTR_EMULATE_PREPARES = true (預設) | 設定 false 強制原生預處理 |
| SELECT rowCount() 回傳 0 | MySQL 驅動不支援 SELECT 筆數統計 | 改用 SELECT COUNT(*) 或 fetchAll() 後 count() |
| 長連接導致 "MySQL server has gone away" | 連線超過 wait_timeout 被斷開 | 啟用 ATTR_PERSISTENT 或重連邏輯 |
| 中文亂碼 | DSN 缺 charset / 連線未設定編碼 | MySQL: charset=utf8mb4;SQL Server: CharacterSet=UTF-8 |
PDO::PARAM_BOOL、PDO::FETCH_KEY_PAIR、錯誤類別層級化PDO 是 PHP 資料庫存取的基石。掌握「DSN 設定」、「錯誤模式設為例外」、「關閉模擬預處理」、「善用交易與自訂封裝」四大要訣,即能寫出安全、高效、可維護的資料層程式碼。無論 MySQL、PostgreSQL、SQL Server、SQLite、Oracle,PDO 都提供一致的開發體驗。