前言
PDO (PHP Data Objects) 提供統一資料庫存取介面,支援 12+ 資料庫驅動。本文彙整核心特性、常用 API、預處理語句、事務處理、錯誤模式等實戰重點。
一、核心特性
- 輕量級 C 擴充套件:效能優於舊式 mysql_* 函數。
- 資料庫無關介面:同一套 API 切換 MySQL/PostgreSQL/SQLite/Oracle/SQL Server 等。
- 預處理語句:防 SQL 注入、提升重複執行效能。
- 事務支援:
beginTransaction()/commit()/rollBack()。
- 大物件 (LOB) 處理:資料流讀寫 BLOB/CLOB。
- 游標捲動:
PDO::CURSOR_SCROLL 支援前向/後向/隨機存取。
- 錯誤模式靈活:Silent/Warning/Exception 三種模式。
- SQLSTATE 錯誤代碼:標準化跨資料庫錯誤識別。
二、連線建立 (DSN)
<?php
// MySQL
$dsn = 'mysql:host=127.0.0.1;port=3306;dbname=test;charset=utf8mb4';
$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::MYSQL_ATTR_INIT_COMMAND => "SET NAMES utf8mb4",
]);
// PostgreSQL
$dsn = 'pgsql:host=localhost;port=5432;dbname=test';
// SQLite
$dsn = 'sqlite:/path/to/database.db';
// SQL Server (sqlsrv 驅動)
$dsn = 'sqlsrv:Server=localhost;Database=test';
三、常用 API 快速查表
| 方法 | 用途 | 回傳值 |
query($sql) | 執行 SELECT,回傳 PDOStatement | PDOStatement|false |
exec($sql) | 執行 INSERT/UPDATE/DELETE/DDL | 影響行數|false |
prepare($sql) | 預處理語句 | PDOStatement |
lastInsertId($seq) | 取得自增 ID | string|false |
beginTransaction() | 啟動事務 | bool |
commit() | 提交事務 | bool |
rollBack() | 回滾事務 | bool |
inTransaction() | 檢查是否在事務中 | bool |
setAttribute() | 設定屬性 | bool |
getAttribute() | 取得屬性 | mixed |
errorCode() | 取得 SQLSTATE | string |
errorInfo() | 取得詳細錯誤資訊 | array |
四、PDOStatement 核心方法
| 方法 | 說明 |
execute($params) | 執行預處理,$params 為陣列綁定參數 |
bindParam($param, $var, $type) | 參數繫結變數參考 |
bindValue($param, $value, $type) | 參數繫結值 |
fetch($mode) | 取得單行 |
fetchAll($mode) | 取得所有行 |
fetchColumn($col) | 取得單欄值 |
rowCount() | 影響行數 (僅 INSERT/UPDATE/DELETE 可靠) |
closeCursor() | 釋放結果集 |
setFetchMode($mode) | 設定預設取得模式 |
getColumnMeta($col) | 取得欄位中繼資料 |
五、實戰範例:預處理 + 事務 + 異常處理
<?php
try {
$pdo->beginTransaction();
// 1. 新增訂單
$stmt = $pdo->prepare('INSERT INTO orders (user_id, total, status) VALUES (?, ?, ?)');
$stmt->execute([$userId, $total, 'pending']);
$orderId = $pdo->lastInsertId();
// 2. 批次新增明細
$stmtDetail = $pdo->prepare('INSERT INTO order_items (order_id, product_id, qty, price) VALUES (?, ?, ?, ?)');
foreach ($items as $item) {
$stmtDetail->execute([$orderId, $item['product_id'], $item['qty'], $item['price']]);
}
// 3. 扣減庫存 (樂觀鎖)
$stmtStock = $pdo->prepare('UPDATE products SET stock = stock - ? WHERE id = ? AND stock >= ?');
foreach ($items as $item) {
$stmtStock->execute([$item['qty'], $item['product_id'], $item['qty']]);
if ($stmtStock->rowCount() === 0) {
throw new \Exception("Product {$item['product_id']} stock insufficient");
}
}
$pdo->commit();
echo "Order $orderId created successfully.";
} catch (\Throwable $e) {
if ($pdo->inTransaction()) $pdo->rollBack();
error_log("Order create failed: " . $e->getMessage());
echo "Operation failed: " . $e->getMessage();
}
六、錯誤處理模式對比
| 模式 | 設定 | 行為 | 推薦場景 |
| Exception | PDO::ATTR_ERRMODE => PDO::ERRMODE_EXCEPTION | 拋出 PDOException | 現代應用、統一 try-catch |
| Warning | PDO::ERRMODE_WARNING | 觸發 E_WARNING | 快速除錯、舊專案相容 |
| Silent | PDO::ERRMODE_SILENT (預設) | 不報錯,需手動檢查 errorCode/errorInfo | 極少用,不推薦 |
七、進階技巧
- 命名參數:
:name 而非 ?,可重複使用、順序無關。
PDO::FETCH_CLASS:直接映射至實體類別 $stmt->fetchAll(PDO::FETCH_CLASS, User::class)。
- 批次插入優化:單一
execute 多組值 INSERT INTO t (a,b) VALUES (?,?), (?,?), ... (支援驅動依賴)。
- 連線池:PHP 無內建連線池,配合 PHP-FPM 持久連線
PDO::ATTR_PERSISTENT => true (慎用,可能耗盡資料庫連線)。
八、官方文件
PHP.net PDO 手冊