| 特性 | bindParam() | bindValue() |
|---|---|---|
| 參數類型 | 變量引用(必須是變量) | 值(變量、字面量、表達式均可) |
| 綁定時機 | 執行 execute() 時讀取變量當前值 | 調用 bindValue() 時即刻綁定值 |
| 存儲過程支援 | 支援 INPUT/OUTPUT/INOUT 參數 | 僅支援 INPUT |
| 大數據/LOB 優勢 | 引用傳遞,避免大字符串複製,內存友好 | 傳值複製,大對象可能佔用雙倍內存 |
<?php
$pdo = new PDO('mysql:host=localhost;dbname=test', 'user', 'pass');
$stmt = $pdo->prepare("SELECT * FROM users WHERE username = :user");
$user = "jack";
// ✅ 正確:bindParam 綁定變量引用
$stmt->bindParam(":user", $user);
// ❌ 錯誤:bindParam 不能直接綁字面量
// $stmt->bindParam(":user", "jack"); // Fatal Error: Cannot pass parameter 2 by reference
// ✅ 正確:bindValue 綁定變量
$stmt->bindValue(":user", $user);
// ✅ 正確:bindValue 綁定字面量
$stmt->bindValue(":user", "jack");
$stmt->execute();
只有 bindParam() 能處理輸出參數:
<?php
$stmt = $pdo->prepare("CALL get_user_count(:total)");
// 必須指定 PARAM_INT 並初始化變量
$total = 0;
$stmt->bindParam(':total', $total, PDO::PARAM_INT | PDO::PARAM_INPUT_OUTPUT, 10); // 長度 10
$stmt->execute();
echo "用戶總數: $total"; // 存儲過程執行後,變量自動更新
' OR '1'='1,也僅作為字符串值匹配,無法改變 SQL 邏輯結構。PDO::ATTR_EMULATE_PREPARES = true 由 PHP 客戶端模擬預處理(拼接轉義),建議關閉 PDO::ATTR_EMULATE_PREPARES = false 強制使用數據庫原生預處理,獲得真正的性能與安全雙重保障。| 數據庫 | 命名參數 | 匿名參數 |
|---|---|---|
| MySQL (PDO) | :name | ? |
| PostgreSQL / SQLite | :name | ? / $1 |
| SQL Server | @name | ? |
| Oracle | :name | :1 |
| Access | 不支援 | ? |
// ❌ 這不是真正的參數化查詢,本質仍是字符串拼接
$query = sprintf("SELECT * FROM users WHERE name='%s' AND pwd='%s'",
mysql_real_escape_string($name),
mysql_real_escape_string($pwd)
);
mysql_query($query);
雖然 mysql_real_escape_string 能防大多數注入,但:
SET NAMES utf8mb4),否則多字節編碼繞過風險。鐵律:凡是動態數據入庫,**無條件使用參數化查詢**(PDO 或 MySQLi prepare)。
bindValue() 更靈活、語義清晰。bindParam() 引用機制不可替代。$pdo->setAttribute(PDO::ATTR_EMULATE_PREPARES, false);。$pdo->setAttribute(PDO::ATTR_ERRMODE, PDO::ERRMODE_EXCEPTION);。