参数化查询是防止SQL注入的核心,需严格分离SQL结构与数据;所有用户输入均不可信,表名、字段名等结构性内容必须白名单校验,不可用占位符。

为什么 mysql_query() 拼接字符串必出问题
因为用户输入直接进 SQL 字符串,' OR '1'='1 这种输入会变成 WHERE name = '' OR '1'='1',整张表被拖走。PHP 5.5+ 已废弃 mysql_*() 系列函数,但还有人用 mysqli_query() 手动拼接,本质一样危险。
- 所有用户可控输入(
$_GET、$_POST、$_COOKIE、文件内容、API 返回值)都算“不可信”,不能直接插进 SQL -
addslashes()或mysql_real_escape_string()不可靠——字符集不匹配时照样绕过 - 预编译不是“加个函数就安全”,核心是“SQL 结构和数据彻底分离”
怎么用 mysqli_prepare() 正确写参数化查询
关键在两步:先定义带占位符的 SQL,再把变量单独绑定进去。MySQL 服务端只认占位符位置,不解析传入的值。
- 占位符统一用
?,不能写成:name或$1(那是 PDO 的语法) - 绑定类型必须准确:
i(整数)、s(字符串)、d(双精度)、b(BLOB),类型错会导致静默截断或报错 - 执行前必须检查
mysqli_prepare()返回值是否为false,否则后续绑定会失败但不报错
/* 正确示例 */
$stmt = mysqli_prepare($conn, "SELECT * FROM users WHERE id = ? AND status = ?");
if ($stmt) {
mysqli_stmt_bind_param($stmt, "is", $user_id, $status);
mysqli_stmt_execute($stmt);
$result = mysqli_stmt_get_result($stmt);
}PDO 的 prepare() 和 execute() 哪些细节容易翻车
PDO 更灵活,但也更容易因配置疏忽漏掉防护。默认不抛异常,错误被吞掉,你以为查到了实际是空结果。
- 必须显式设置
PDO::ATTR_ERRMODE => PDO::ERRMODE_EXCEPTION,否则prepare()失败返回false,你得自己判断 -
bindValue()和bindParam()区别很大:bindValue()传值,bindParam()传引用——循环中复用同一个变量时,后者会出错 - 不要用
exec()或query()执行带用户输入的语句,哪怕你“确认过没单引号”——逻辑可能变,输入源可能增
$pdo->setAttribute(PDO::ATTR_ERRMODE, PDO::ERRMODE_EXCEPTION);
$stmt = $pdo->prepare("INSERT INTO logs (msg, level) VALUES (?, ?)");
$stmt->execute([$message, $level]); // execute() 内部自动 bindValue哪些场景看似安全实则仍需参数化
表名、字段名、ORDER BY 字段、LIMIT 数值……这些无法用 ? 占位,但很多人误以为“没字符串拼接就没事”。错。
-
ORDER BY ?是语法错误,MySQL 不允许占位符出现在结构位置 - 解决方案只有白名单校验:
in_array($sort_field, ['created_at', 'name'], true),不匹配就拒掉 -
LIMIT后的数字必须转整型:(int)$limit或filter_var($limit, FILTER_VALIDATE_INT),不能只intval() - 动态构建 WHERE 条件时,每个子句的字段名/操作符都要单独白名单,不能只校验最终拼出的 SQL 字符串
预编译解决的是“数据混入 SQL”的问题,不是“SQL 本身可构造”的问题。字段名、排序方式、分页偏移量这些结构性内容,永远不能靠参数化兜底——它们得靠严格校验或硬编码控制。


















