首頁
編程語言
C語言|JAVA編程
Python編程
網頁編程
ASP編程|PHP編程
JSP編程
數據庫
MYSQL數據庫|SqlServer數據庫
Oracle數據庫|DB2數據庫
您现在的位置: 程式碼之家 >> 編程語言 >  >> 網頁程式設計 >> PHP 程式設計 >> PHP 綜合知識

PHP PDO 深度解析:從連線管理到預處理語句的最佳實踐

     編輯:PHP 綜合知識

為什麼選擇 PDO?

PHP Data Objects (PDO) 提供統一的資料庫存取介面,支援 12 種資料庫驅動程式,是現代 PHP 專案的標準選擇。本文系統整理 PDO 核心方法、錯誤處理、交易控制與實戰技巧。

核心方法速查表

方法用途回傳值
PDO::query()執行 SELECT 等有結果集的查詢PDOStatement 物件
PDO::exec()執行 INSERT/UPDATE/DELETE 等無結果集操作影響行數 (integer)
PDO::lastInsertId()取得最後插入的自增 ID字串格式的 ID
PDOStatement::fetch()逐行取得單筆記錄陣列/物件/false
PDOStatement::fetchAll()一次性取得所有記錄二維陣列
PDOStatement::fetchColumn()直接取得指定欄位值單一欄位值
PDOStatement::rowCount()取得 SELECT 影響行數整數
PDOStatement::columnCount()取得結果集欄位數整數

資料擷取模式 (fetch_style) 詳細說明

// 預設:同時回傳關聯與數字索引
$rows = $stmt->fetchAll(PDO::FETCH_BOTH);

// 僅關聯陣列 (最常用,記憶體效率佳)
$rows = $stmt->fetchAll(PDO::FETCH_ASSOC);

// 僅數字索引陣列
$rows = $stmt->fetchAll(PDO::FETCH_NUM);

// 回傳物件 (屬性對應欄位名)
$rows = $stmt->fetchAll(PDO::FETCH_OBJ);

// 指定類別映射 (進階 ORM 雛形)
$stmt->setFetchMode(PDO::FETCH_CLASS, 'UserEntity');
while ($user = $stmt->fetch()) {
    echo $user->getFullName();
}

連線建立與錯誤處理模式

基本連線 (支援持久連線)

$dsn = 'mysql:host=localhost;port=3306;dbname=test;charset=utf8mb4';
$options = [
    PDO::ATTR_PERSISTENT => true,        // 持久連線
    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) {
    error_log("資料庫連線失敗: " . $e->getMessage());
    die("系統暫時無法服務,請稍後再試");
}

三種錯誤模式比較

  • ERRMODE_SILENT:預設值,靜默失敗,需手動檢查 errorInfo()
  • ERRMODE_WARNING:觸發 E_WARNING,不中斷執行
  • ERRMODE_EXCEPTION:推薦,拋出 PDOException,便於統一錯誤處理

交易控制:確保資料一致性

try {
    $pdo->beginTransaction();
    
    $pdo->exec("INSERT INTO `orders` (`user_id`, `amount`) VALUES (1001, 299.00)");
    $orderId = $pdo->lastInsertId();
    
    $pdo->exec("INSERT INTO `order_items` (`order_id`, `product_id`, `qty`) VALUES ($orderId, 50, 2)");
    $pdo->exec("UPDATE `products` SET `stock` = `stock` - 2 WHERE `id` = 50");
    
    $pdo->commit();
    echo "訂單建立成功";
} catch (Exception $e) {
    $pdo->rollBack();
    error_log("訂單交易失敗: " . $e->getMessage());
    throw $e;
}

預處理語句:防禦 SQL 注入的利器

命名參數 vs 位置參數

// 命名參數 (PDO 專屬,可讀性高)
$sql = "SELECT * FROM `users` WHERE `email` = :email AND `status` = :status";
$stmt = $pdo->prepare($sql);
$stmt->execute([':email' => $email, ':status' => 1]);

// 位置參數 (相容性較好)
$sql = "SELECT * FROM `users` WHERE `email` = ? AND `status` = ?";
$stmt = $pdo->prepare($sql);
$stmt->execute([$email, 1]);

// 批次插入優化
$sql = "INSERT INTO `logs` (`level`, `message`, `created_at`) VALUES (?, ?, NOW())";
$stmt = $pdo->prepare($sql);
foreach ($logEntries as $entry) {
    $stmt->execute([$entry['level'], $entry['message']]);
}

進階綁定技巧

// 明確指定資料類型
$stmt->bindValue(':id', $id, PDO::PARAM_INT);
$stmt->bindValue(':name', $name, PDO::PARAM_STR);
$stmt->bindValue(':data', $json, PDO::PARAM_LOB);

// 參考綁定 (適合迴圈大量資料)
$stmt->bindParam(':status', $status, PDO::PARAM_INT);
foreach ($statuses as $status) {
    $stmt->execute();
}

實戰完整範例:CRUD 操作封裝

class UserRepository {
    private PDO $pdo;
    
    public function __construct(PDO $pdo) {
        $this->pdo = $pdo;
    }
    
    public function create(array $data): int {
        $sql = "INSERT INTO `users` (`email`, `name`, `password_hash`) VALUES (:email, :name, :pass)";
        $stmt = $this->pdo->prepare($sql);
        $stmt->execute([
            ':email' => $data['email'],
            ':name' => $data['name'],
            ':pass' => password_hash($data['password'], PASSWORD_BCRYPT)
        ]);
        return (int)$this->pdo->lastInsertId();
    }
    
    public function findByEmail(string $email): ?array {
        $stmt = $this->pdo->prepare("SELECT * FROM `users` WHERE `email` = :email LIMIT 1");
        $stmt->execute([':email' => $email]);
        return $stmt->fetch() ?: null;
    }
    
    public function updateLastLogin(int $userId): bool {
        $stmt = $this->pdo->prepare("UPDATE `users` SET `last_login` = NOW() WHERE `id` = :id");
        return $stmt->execute([':id' => $userId]);
    }
}

效能優化關鍵點

  1. 小結果集用 fetchAll:減少網路往返;大結果集用 fetch 迴圈:避免記憶體暴增
  2. 善用索引:WHERE/JOIN/ORDER BY 欄位必須建立索引
  3. 避免 SELECT *:只查需要的欄位,減少傳輸量
  4. 批次操作用交易:大量寫入時包裹在 transaction 中,效能提升數倍
  5. 關閉模擬預處理:ATTR_EMULATE_PREPARES => false 使用資料庫原生準備語句

常見坑點提醒

  • lastInsertId() 在多筆 INSERT 中只返回第一筆 ID
  • rowCount() 對 SELECT 在某些驅動不可靠,建議改用 COUNT(*)
  • MySQL 需在 DSN 指定 charset=utf8mb4 而非執行 SET NAMES
  • 持久連線可能導致「MySQL server has gone away」,長連線需配合 wait_timeout 設定

結語

PDO 不只是資料庫抽象層,更是寫出安全、可維護、可攜遷 PHP 程式的基石。掌握預處理語句、交易控制、錯誤處理三大核心,即可應付 90% 的實務開發場景。建議將常用操作封裝成 Repository 模式,進一步提升程式碼品質。

  1. 上一篇文章:
  2. 下一篇文章:
Copyright © 程式碼之家 All Rights Reserved