在使用 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 的职责划分,才能编写出更健壮、更安全的数据库交互代码。