PHP 連接 MySQL 有兩大主流選擇:PDO (PHP Data Objects) 與 mysqli (MySQL Improved)。兩者皆支援預處理、物件導向、交易控制,但在資料庫支援度、API 設計、效能細節上有關鍵差異。本文從架構、功能、程式碼風格、遷移成本五個維度深度對比,助你做出專案最佳選擇。
| 維度 | PDO | mysqli |
|---|---|---|
| 資料庫支援 | 12+ 種 (MySQL, PostgreSQL, SQLite, Oracle, SQL Server...) | 僅 MySQL/MariaDB |
| API 風格 | 純物件導向 | 雙模式:OOP + 程序式 |
| 命名參數 | 支援 :name | 不支援,僅 ? 位置佔位符 |
| 物件映射 (ORM) | PDO::FETCH_CLASS 直接映射 | fetch_object('Class') 需指定類別 |
| 預處理 (Client-side) | 支援 (模擬) | 不支援 (僅 Server-side) |
| 效能 (單純 MySQL) | 極微幅較慢 (封裝層) | 原生較快 |
| 連線字串 (DSN) | 統一格式 mysql:host=...;dbname=... | 分離參數 host, user, pass, db |
| 錯誤模式 | 例外、警告、靜默三模式可切 | 同樣三模式 |
// PDO - 統一 DSN,例外模式
$pdo = new PDO("mysql:host=127.0.0.1;dbname=test;charset=utf8mb4", 'user', 'pass', [
PDO::ATTR_ERRMODE => PDO::ERRMODE_EXCEPTION,
PDO::ATTR_DEFAULT_FETCH_MODE => PDO::FETCH_ASSOC,
PDO::ATTR_EMULATE_PREPARES => false, // 使用真實預處理
]);
// mysqli OOP
$mysqli = new mysqli('127.0.0.1', 'user', 'pass', 'test', 3306);
if ($mysqli->connect_errno) throw new Exception($mysqli->connect_error);
$mysqli->set_charset('utf8mb4');
$mysqli->report_mode = MYSQLI_REPORT_ERROR | MYSQLI_REPORT_STRICT; // 例外模式
// mysqli 程序式
$link = mysqli_connect('127.0.0.1', 'user', 'pass', 'test');
mysqli_set_charset($link, 'utf8mb4');
mysqli_report(MYSQLI_REPORT_ERROR | MYSQLI_REPORT_STRICT);
// PDO - 命名參數,陣列綁定,程式碼可讀性高
$stmt = $pdo->prepare("SELECT * FROM users WHERE email = :email AND status = :status");
$stmt->execute([':email' => $email, ':status' => 'active']);
$user = $stmt->fetch();
// mysqli - 位置佔位符,需按順序 bind_param,型別字串易錯
$stmt = $mysqli->prepare("SELECT * FROM users WHERE email = ? AND status = ?");
$stmt->bind_param('ss', $email, 'active');
$stmt->execute();
$result = $stmt->get_result();
$user = $result->fetch_assoc();
class User {
public int $id;
public string $name;
public function label(): string { return "#{$this->id} {$this->name}"; }
}
// PDO - 一行設定映射模式
$stmt = $pdo->query("SELECT id, name FROM users");
$stmt->setFetchMode(PDO::FETCH_CLASS, User::class);
foreach ($stmt as $user) {
echo $user->label() . "\n";
}
// mysqli - 每次 fetch 指定類別
$result = $mysqli->query("SELECT id, name FROM users");
while ($user = $result->fetch_object(User::class)) {
echo $user->label() . "\n";
}
兩者語法幾乎相同:
// PDO
$pdo->beginTransaction();
try {
$pdo->exec("INSERT ...");
$pdo->exec("UPDATE ...");
$pdo->commit();
} catch (Exception $e) {
$pdo->rollBack();
throw $e;
}
// mysqli
$mysqli->begin_transaction();
try {
$mysqli->query("INSERT ...");
$mysqli->query("UPDATE ...");
$mysqli->commit();
} catch (Exception $e) {
$mysqli->rollback();
throw $e;
}
PDO::ERRMODE_EXCEPTION / MYSQLI_REPORT_ERROR | MYSQLI_REPORT_STRICT,避免靜默失敗。PDO::ATTR_EMULATE_PREPARES => false,確保真實 Server-side Prepare,防止邊緣注入風險。Database 抽象層:專案僅依賴介面,底層可熱插拔 PDO/mysqli。PDO 與 mysqli 皆為成熟穩定的擴展,核心差異在於「跨資料庫抽象」vs「MySQL 專用極致效能」。理解兩者 API 細節與權衡,才能在架構演進中做出正確選擇。