在使用PHP 的mysqli擴展進行數據庫操作時,預處理語句是一種常見且安全的做法。開發者通常會使用mysqli::stmt_init()來初始化一個語句對象,然後通過prepare()方法準備SQL 語句。然而,如果在調用stmt_init()後忘記執行prepare() ,將會導致一些隱性問題,本文將對此進行分析和舉例說明。
通常,我們的代碼結構是這樣的:
$mysqli = new mysqli("localhost", "user", "password", "database");
$stmt = $mysqli->stmt_init();
$stmt->prepare("SELECT * FROM users WHERE id = ?");
其中:
stmt_init()初始化一個mysqli_stmt對象;
prepare()準備一個SQL 語句,並與該對象綁定。
如果你只調用了stmt_init()而沒有調用prepare() ,程序在後續使用該語句對象時將出現以下幾類問題:
在未調用prepare()的情況下直接調用bind_param() 、 execute()等方法,會導致拋出警告甚至致命錯誤:
$mysqli = new mysqli("localhost", "user", "password", "database");
$stmt = $mysqli->stmt_init();
// 忘記調用 prepare()
$stmt->bind_param("i", $userId); // 這裡將報錯
$stmt->execute(); // 這裡也將失敗
錯誤提示可能是:
Fatal error: Uncaught Error: Call to a member function bind_param() on bool
或者:
Warning: mysqli_stmt::bind_param(): invalid object or not initialized
如果你只是初始化了語句對象卻沒有prepare() ,那麼原本可以在prepare()階段發現的SQL 錯誤將被隱藏,直到執行時才可能導致不明確的錯誤。這會增加調試成本。
忘記使用prepare()意味著你可能直接拼接SQL 語句,這將繞過參數綁定的機制,從而增加SQL 注入風險。例如:
// 錯誤示例(沒有 prepare)
$userId = $_GET['id'];
$query = "SELECT * FROM users WHERE id = $userId";
$result = $mysqli->query($query);
如果沒有prepare()和參數綁定,用戶輸入將直接嵌入SQL 中,攻擊者可以構造如下URL:
https://m66.net/get_user.php?id=1 OR 1=1
將導致數據洩露。
始終配對使用stmt_init()和prepare() ,如下所示:
$mysqli = new mysqli("localhost", "user", "password", "database");
$stmt = $mysqli->stmt_init();
if ($stmt->prepare("SELECT * FROM users WHERE id = ?")) {
$stmt->bind_param("i", $userId);
$stmt->execute();
$result = $stmt->get_result();
while ($row = $result->fetch_assoc()) {
echo $row['username'] . "<br>";
}
$stmt->close();
} else {
echo "SQL prepare 錯誤: " . $mysqli->error;
}
忘記調用prepare()會導致以下問題:
調用其他語句方法時出現錯誤;
SQL 錯誤無法提前被捕捉;
增加SQL 注入等安全風險。
建議在使用stmt_init()後總是緊接著調用prepare() ,並對其返回值進行判斷,確保程序的穩定性與安全性。