預處理語句是防禦 SQL 注入的黃金標準,同時透過查詢計劃重用提升效能。本文以 mysqli 面向物件 API 為例,詳細講解 prepare → bind_param → bind_result → execute → fetch 完整流程。
$stmt = $mysqli->prepare($sql) —— SQL 含 ? 佔位符$stmt->bind_param('types', $var1, ...) —— 類型字串:i(整數)/d(浮點)/s(字串)/b(blob)$stmt->bind_result($col1, $col2, ...) —— 對應 SELECT 欄位順序$stmt->execute()while ($stmt->fetch()) { ... }$stmt->free_result(); $stmt->close();<?php
$mysqli = new MySQLi("localhost", "root", "123456", "liuyan");
if ($mysqli->connect_error) {
die("連線失敗: " . $mysqli->connect_error);
}
$sql = "SELECT id, title, contents FROM news WHERE id > ?";
$stmt = $mysqli->prepare($sql);
$id = 5; // 只查 id > 5
$stmt->bind_param("i", $id); // i = integer
$stmt->bind_result($id, $title, $contents);
$stmt->execute();
while ($stmt->fetch()) {
echo "--$id--$title--$contents--<hr>";
}
$stmt->free_result();
$stmt->close();
$mysqli->close();
?>
| 細節 | 說明 |
|---|---|
| 類型字串 | 對應變數數量必須一致;bind_param('iss', $id, $name, $content) |
| 參數傳址 | PHP 5.3+ 必須傳變數,不可直接傳常數:bind_param('i', 5) ❌ → $v=5; bind_param('i', $v) ✅ |
| 結果綁定順序 | 嚴格對應 SELECT 列順序,欄位名不重要 |
| 多結果集 | 預處理不支援 multi_query,需改用一般查詢 |
| 錯誤處理 | $stmt->error、$stmt->errno 取得最後錯誤 |
function queryPrepared($mysqli, $sql, $types, $params, $columns) {
$stmt = $mysqli->prepare($sql);
if (!$stmt) return false;
$stmt->bind_param($types, ...$params);
$stmt->execute();
$stmt->bind_result(...$columns);
$results = [];
while ($stmt->fetch()) {
$row = [];
foreach ($columns as $i => $col) $row[$col] = $$col;
$results[] = $row;
}
$stmt->close();
return $results;
}
// 使用
$rows = queryPrepared($mysqli,
"SELECT id, title FROM news WHERE id > ? AND status = ?",
"is", [$minId, 1],
['id', 'title']
);
掌握預處理是 PHP 後端進階的必修課。建議在專案中建立資料庫存取層(DAL)強制使用預處理,杜絕手寫拼接 SQL。