PDO::exec() 專門用於執行 不返回結果集 的 SQL 語句:INSERT、UPDATE、DELETE、CREATE TABLE、TRUNCATE 等。返回值為 受影響行數(整數),失敗返回 false。
| 方法 | 用途 | 返回值 | 典型場景 |
|---|---|---|---|
exec() | 無結果集 SQL | 受影響行數 (int) / false | INSERT/UPDATE/DELETE/DDL |
query() | 有結果集 SQL | PDOStatement 對象 / false | SELECT/SHOW/DESCRIBE |
prepare() + execute() | 參數化/重複執行 | bool (execute) / PDOStatement | 所有場景(推薦首選) |
<?php
header('Content-Type: text/html; charset=utf-8');
$dsn = 'mysql:host=localhost;dbname=test;charset=utf8mb4';
$pdo = new PDO($dsn, 'root', '123456', [
PDO::ATTR_ERRMODE => PDO::ERRMODE_EXCEPTION, // 拋出異常,不用手動檢查錯誤碼
PDO::ATTR_DEFAULT_FETCH_MODE => PDO::FETCH_ASSOC,
PDO::ATTR_EMULATE_PREPARES => false, // 使用原生預處理
]);
// 1. 執行 INSERT,獲取影響行數
$username = 'liming';
$password = password_hash('123456', PASSWORD_DEFAULT); // 安全:密碼哈希
$regdate = time();
$sql = "INSERT INTO userlist (username, password, regdate) VALUES (?, ?, ?)";
$affected = $pdo->exec($sql); // ❌ 錯誤:exec 不支持參數綁定,會字面執行 "?"
// 正確寫法:prepare + execute
$stmt = $pdo->prepare($sql);
$stmt->execute([$username, $password, $regdate]);
$affected = $stmt->rowCount(); // 受影響行數
echo "寫入 {$affected} 條記錄!<hr>";
// 2. 獲取自增 ID(INSERT 專用)
$lastId = $pdo->lastInsertId();
echo "新記錄 ID: {$lastId}<hr>";
// 3. 查詢驗證(使用 query 返回 PDOStatement)
$stmt = $pdo->query("SELECT * FROM userlist ORDER BY id DESC LIMIT 5");
while ($row = $stmt->fetch()) {
echo "用戶名: {$row['username']} ";
echo "註冊時間: " . date('Y-m-d H:i:s', $row['regdate']) . "<br>";
}
// ❌ 錯誤:SQL 注入風險,且 ? 會被當作字面字符
$pdo->exec("INSERT INTO t (c) VALUES (?)");
// ✅ 正確:prepare + execute 參數化
$stmt = $pdo->prepare("INSERT INTO t (c) VALUES (?)");
$stmt->execute([$value]);
結論:生產代碼中幾乎不應直接使用 exec(),除非執行純靜態 DDL(如 CREATE TABLE、TRUNCATE)。所有帶參數的寫操作統一用 prepare() + execute()。
try {
$pdo->beginTransaction();
$pdo->prepare("INSERT INTO orders (user_id, total) VALUES (?, ?)")->execute([1, 99.9]);
$orderId = $pdo->lastInsertId();
$pdo->prepare("INSERT INTO order_items (order_id, product_id, qty) VALUES (?, ?, ?)")
->execute([$orderId, 101, 2]);
$pdo->prepare("UPDATE products SET stock = stock - 2 WHERE id = 101")->execute();
$pdo->commit();
echo "訂單創建成功";
} catch (Throwable $e) {
$pdo->rollBack();
echo "失敗回滾: " . $e->getMessage();
}
// 方案 A:循環 prepare + execute(慢,每條往返)
foreach ($rows as $row) {
$stmt->execute($row);
}
// 方案 B:構建多值 INSERT(快,單次往返)
$placeholders = implode(',', array_fill(0, count($rows), '(?,?,?)'));
$sql = "INSERT INTO logs (level, message, created_at) VALUES {$placeholders}";
$params = array_merge(...$rows); // 展平為一維數組
$pdo->prepare($sql)->execute($params);
| 模式 | 設置 | 代碼風格 | 推薦度 |
|---|---|---|---|
| 靜默 | ATTR_ERRMODE => SILENT | 手動檢查 $stmt->errorCode() | ❌ 易漏檢 |
| 警告 | ATTR_ERRMODE => WARNING | 觸發 E_WARNING,繼續執行 | ❌ 非致命 |
| 異常 | ATTR_ERRMODE => EXCEPTION | 拋出 PDOException,try-catch 捕獲 | ✅ 強制處理 |
exec() 僅用於無參數 DDL,返回影響行數。prepare() + execute() 參數化,防注入、支援二進制數據、可重複執行。lastInsertId() 獲取自增 ID。ERRMODE_EXCEPTION + try-catch 統一錯誤處理。