在使用 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(),并对其返回值进行判断,确保程序的稳定性与安全性。