PHP 是 MySQL 應用最廣泛的語言。從早期的 mysql_*(已移除)、mysqli 到現代的 PDO,API 經歷了巨大演進。本文系統梳理核心 API、預處理機制、錯誤處理模式,並確立「PDO + 預處理 + 異常模式」的現代最佳實踐標準。
| 擴展 | 狀態 | 特點 |
|---|---|---|
mysql_* | PHP 7.0 移除 | 過程式、無預處理、不支援新特性、極不安全 |
mysqli | 維護中 | MySQL 專用、支援預處理、程序/物件雙風格、功能最全 |
PDO | 推薦標準 | 資料庫抽象層、支援 12+ 驅動、統一 API、原生預處理、異常模式 |
| 操作 | mysqli (OO) | PDO |
|---|---|---|
| 連線 | new mysqli($h,$u,$p,$db) | new PDO($dsn,$u,$p) |
| 設定字符集 | $mysqli->set_charset('utf8mb4') | DSN charset=utf8mb4 |
| 預處理 | $stmt = $mysqli->prepare($sql) | $stmt = $pdo->prepare($sql) |
| 綁參執行 | $stmt->bind_param($types, ...$vals); $stmt->execute() | $stmt->execute($vals) 或 bindValue |
| 獲取結果 | $res = $stmt->get_result(); $row = $res->fetch_assoc() | $row = $stmt->fetch(PDO::FETCH_ASSOC) |
| 影響行數 | $stmt->affected_rows | $stmt->rowCount() |
| 自增 ID | $mysqli->insert_id | $pdo->lastInsertId() |
| 事務 | $mysqli->begin_transaction(); commit(); rollback() | $pdo->beginTransaction(); commit(); rollBack() |
| 錯誤模式 | mysqli_report(MYSQLI_REPORT_ERROR | MYSQLI_REPORT_STRICT) | PDO::ATTR_ERRMODE => PDO::ERRMODE_EXCEPTION |
| 關閉 | $mysqli->close() | $pdo = null |
<?php
// 1. 連線配置
$dsn = 'mysql:host=localhost;port=3306;dbname=myapp;charset=utf8mb4';
$options = [
PDO::ATTR_ERRMODE => PDO::ERRMODE_EXCEPTION, // 拋出異常
PDO::ATTR_DEFAULT_FETCH_MODE => PDO::FETCH_ASSOC,
PDO::ATTR_EMULATE_PREPARES => false, // 原生預處理
PDO::ATTR_STRINGIFY_FETCHES => false, // 保持原生類型
PDO::MYSQL_ATTR_SSL_CA => '/etc/ssl/certs/ca.pem', // 生產環境 SSL
];
try {
$pdo = new PDO($dsn, 'app_user', 'strong_password', $options);
} catch (PDOException $e) {
error_log('DB Connect Failed: ' . $e->getMessage());
die('Service unavailable');
}
// 2. 查詢範例:預處理 + 綁定 + 異常捕獲
function getUserByEmail(PDO $pdo, string $email): ?array {
$sql = 'SELECT id, name, email, created_at FROM users WHERE email = ? AND deleted_at IS NULL';
$stmt = $pdo->prepare($sql);
$stmt->execute([$email]);
return $stmt->fetch() ?: null;
}
// 3. 寫入範例:事務 + 預處理 + 回滾
function createOrder(PDO $pdo, int $userId, array $items): int {
$pdo->beginTransaction();
try {
$stmt = $pdo->prepare('INSERT INTO orders (user_id, total, status) VALUES (?, ?, "pending")');
$total = array_sum(array_column($items, 'price'));
$stmt->execute([$userId, $total]);
$orderId = (int)$pdo->lastInsertId();
$stmtItem = $pdo->prepare('INSERT INTO order_items (order_id, product_id, qty, price) VALUES (?, ?, ?, ?)');
foreach ($items as $item) {
$stmtItem->execute([$orderId, $item['product_id'], $item['qty'], $item['price']]);
}
$pdo->commit();
return $orderId;
} catch (Throwable $e) {
$pdo->rollBack();
throw $e;
}
}
// 4. 批次插入:單條 SQL 多值
function batchInsertLogs(PDO $pdo, array $logs): int {
if (!$logs) return 0;
$placeholders = implode(',', array_fill(0, count($logs), '(?, ?, ?)'));
$sql = "INSERT INTO access_logs (ip, path, created_at) VALUES $placeholders";
$stmt = $pdo->prepare($sql);
$vals = [];
foreach ($logs as $log) {
$vals[] = $log['ip'];
$vals[] = $log['path'];
$vals[] = $log['created_at'];
}
$stmt->execute($vals);
return $stmt->rowCount();
}
?>
<?php
mysqli_report(MYSQLI_REPORT_ERROR | MYSQLI_REPORT_STRICT); // 異常模式
$mysqli = new mysqli('localhost', 'user', 'pass', 'db', 3306);
$mysqli->set_charset('utf8mb4');
$stmt = $mysqli->prepare('SELECT id, name FROM users WHERE email = ?');
$stmt->bind_param('s', $email);
$stmt->execute();
$result = $stmt->get_result();
while ($row = $result->fetch_assoc()) { /* ... */ }
$stmt->close();
$mysqli->close();
?>
?(PDO)或 ?(mysqli),絕不拼接變數進 SQL 字串。if (in_array($col, $allowed)) $sql .= "`$col`";PDOException: SQLSTATE[HY000] [2002] Connection refused:服務未啟動/埠錯/防火牆/主機名解析。mysqli::prepare(): Couldn't fetch mysqli:連線已關閉或連線失敗未檢查。Packets out of order:連線被併發使用(mysqli 非線程安全),每請求獨立連線或用連線池。Commands out of sync:多結果集未全部讀取/釋放,$stmt->free_result() / $stmt->next_result()。PDO::ATTR_PERSISTENT => true / p:host)。INSERT ... VALUES (...), (...), ... 单條 SQL 寫入成百上千行。SELECT id, name FROM ... 而非 SELECT *。PHP 操作 MySQL 的核心哲學:PDO 統一介面、預處理防注入、異常模式處錯、連線池復用、批次寫入、最小權限帳號。將這套標準內化為團隊規範、封裝為基礎庫、納入 Code Review 清單,讓每一行資料庫代碼都安全、高效、可維護。