在使用PHP 的mysqli擴展進行數據庫操作時,我們常常會使用預處理語句(Prepared Statements)來提高安全性和性能。與此同時,開發者通常也希望在執行過程中準確捕捉到可能發生的錯誤,例如SQL 語法錯誤。但你可能會遇到這樣一個令人困惑的問題:
那麼,這到底是怎麼回事呢?本文將對這個問題進行深入分析,並提供實用的解決方案。
看下面這段示例代碼:
$mysqli = new mysqli("localhost", "user", "password", "testdb");
// 語法錯誤:少了一個 FROM
$sql = "SELECT id name users WHERE id = ?";
$stmt = $mysqli->prepare($sql);
if (!$stmt) {
echo "Prepare failed: " . $mysqli->error; // 正確做法
} else {
$stmt->bind_param("i", $id);
$id = 1;
$stmt->execute();
if ($stmt->error) {
echo "Execute error: " . $stmt->error; // 不会捕捉到語法錯誤
}
$stmt->close();
}
$mysqli->close();
Prepare failed: You have an error in your SQL syntax; check the manual that corresponds to your MySQL server version...
可以看到, SQL 語法錯誤根本不會進入$stmt->error ,而是在prepare()階段由$mysqli->error捕捉到。
我們需要理解PHP 中mysqli和mysqli_stmt之間的分工:
mysqli::prepare()是數據庫連接對象調用的方法。如果SQL 有語法錯誤,根本不會生成stmt對象,因此$stmt是false 。
mysqli_stmt::$error只在語句成功prepare 後,並且發生運行時錯誤(如執行時綁定變量類型不符、外鍵約束失敗等)時才有值。
所以, SQL 語法錯誤不會也不能通過$stmt->error捕捉到,因為它甚至不會創建stmt 對象。
換句話說:
SQL 語法錯誤發生在prepare()階段,必須通過$mysqli->error或mysqli::prepare()的返回值判斷,而不是等到$stmt->execute() 。
改寫上面的代碼,採用更穩妥的錯誤處理邏輯:
$mysqli = new mysqli("localhost", "user", "password", "testdb");
$sql = "SELECT id, name FROM users WHERE id = ?"; // 正確語法
$stmt = $mysqli->prepare($sql);
if (!$stmt) {
// 检查語法錯誤
die("SQL prepare failed: " . $mysqli->error);
}
$stmt->bind_param("i", $id);
$id = 1;
if (!$stmt->execute()) {
// 檢查運行時錯誤
die("Execute failed: " . $stmt->error);
}
$result = $stmt->get_result();
$data = $result->fetch_assoc();
echo "User: " . $data['name'];
$stmt->close();
$mysqli->close();
假如你在使用AJAX 接口提交SQL 參數,比如訪問一個這樣的地址:
https://gitbox.net/api/get_user.php?id=1
然後你把用戶輸入拼接進SQL(危險!):
$id = $_GET['id'];
$sql = "SELECT * FROM users WHERE id = $id"; // 潛在的 SQL 注入風險
這種寫法很容易在拼接時寫錯SQL,而且prepare()也不會用到,錯誤也更難定位。因此,更推薦如下寫法:
$sql = "SELECT * FROM users WHERE id = ?";
$stmt = $mysqli->prepare($sql);
$stmt->bind_param("i", $_GET['id']);
這樣不僅避免了SQL 注入,也讓錯誤檢查變得明確、集中。
SQL 語法錯誤只能在prepare()階段被發現,不能通過$stmt->error獲取。
務必檢查prepare()的返回值,並用$mysqli->error輸出錯誤信息。
建議使用預處理語句防止注入,也更容易調試和捕捉錯誤。
通過理解mysqli和mysqli_stmt的職責劃分,才能編寫出更健壯、更安全的數據庫交互代碼。