预处理语句防SQL注入的核心是SQL结构与数据彻底分离,必须全程使用prepare→bind→execute流程且杜绝任何字符串拼接;动态表名/字段名、ORDER BY等需白名单校验,PDO须禁用模拟预处理。

PHP 使用预处理语句(Prepared Statements)本身不自动防注入,但它是目前最可靠、最主流的防 SQL 注入手段——前提是正确使用 mysqli 或 PDO 的预处理接口,并且**所有用户输入都必须通过参数绑定传入,不能拼接进 SQL 字符串**。
为什么预处理能防注入?
核心在于“SQL 结构”和“数据”彻底分离:数据库先编译 SQL 模板(如 SELECT * FROM users WHERE id = ?),再把用户输入作为纯数据绑定进去。即使输入是 ' OR 1=1 -- ,数据库也只当它是字符串值,不会参与语法解析。
常见错误现象:
- 用
mysqli_real_escape_string()处理后再拼进 SQL —— 这不是预处理,且在多字节编码等场景下可能失效 - 只对部分参数用
bind_param(),其他仍用字符串拼接 —— 只要一处拼接,整条语句就失守 - 用
mysql_query()(已废弃)或未启用PDO::ATTR_EMULATE_PREPARES = false—— 启用模拟预处理时,PDO 实际仍是拼接,不安全
mysqli 预处理的正确写法
必须走 prepare() → bind_param() → execute() 流程,且类型标识符(s/i/d/b)要匹配实际数据类型。
立即学习“PHP免费学习笔记(深入)”;
示例(查询用户):
$stmt = $mysqli->prepare("SELECT name, email FROM users WHERE status = ? AND age > ?");
$stmt->bind_param("si", $status, $min_age); // "si" 表示 string + int
$status = "active";
$min_age = 18;
$stmt->execute();
$result = $stmt->get_result();
注意点:
-
bind_param()第一个参数是类型字符串,顺序必须与?占位符一致 - 变量必须是引用(
bind_param()内部按引用传递),所以不能直接写bind_param("s", "active") - 如果需要动态表名/字段名(如排序字段),预处理无法处理——这些必须白名单校验后硬拼,不能交给用户控制
PDO 预处理的关键配置
PDO 默认开启模拟预处理(PDO::ATTR_EMULATE_PREPARES = true),此时 prepare() 和 execute() 只是 PHP 层拼接,不经过 MySQL 的 prepare 流程,失去防注入意义。
务必显式关闭模拟:
$pdo = new PDO($dsn, $user, $pass, [
PDO::ATTR_EMULATE_PREPARES => false,
PDO::ATTR_ERRMODE => PDO::ERRMODE_EXCEPTION,
]);
$stmt = $pdo->prepare("INSERT INTO logs (ip, action) VALUES (?, ?)");
$stmt->execute([$client_ip, $action]);
使用场景差异:
- 用
execute([$val1, $val2])最简洁;需复用时可先bindValue()或bindParam() -
bindParam()绑定的是变量引用,适合循环执行同一条语句(如批量插入) - 不要混用命名占位符(
:name)和问号占位符(?)在同一语句中
容易被忽略的边界情况
预处理不是万能银弹,以下情况仍需额外防护:
- ORDER BY / GROUP BY / LIMIT 后的字段或数值:MySQL 不允许参数化这些位置,必须用白名单过滤(如
in_array($sort, ['id', 'name', 'created_at'])) - LIKE 模糊查询中的通配符:
%和_是数据内容,不是 SQL 语法,但需注意是否应由用户输入决定——若用户输入含%,应明确是否允许,必要时用addcslashes($keyword, '%_\\')转义并配合ESCAPE - JSON 字段更新、SET 子句动态列:这类 DML 动态性高,预处理无法覆盖,需严格校验字段名+值类型,或改用 ORM 的安全方法
真正安全的预处理,从来不是调用了 prepare() 就完事,而是整个数据流向里,没有一行 SQL 字符串拼接操作,也没有任何用户输入绕过绑定流程直抵查询构造环节。











