pdo::prepare() + execute() 是唯一可靠方式,因 mysqli_real_escape_string() 无法处理数字上下文、order by 字段名等场景,且 php 8.3 已弃用;必须禁用模拟预处理并配置错误模式与默认获取方式。

直接用 PDO::prepare() + execute() 是唯一可靠的方式,其他任何字符串拼接、手动转义或“差不多安全”的做法,在 PHP 8.3 环境下都可能被绕过。
为什么 mysqli_real_escape_string() 不再够用
这个函数只对单引号、反斜杠等做转义,但无法处理数字上下文、ORDER BY 后字段名、UNION 查询中的列名等场景。攻击者输入 1 OR 1=1 或 id ASC, (SELECT password FROM users LIMIT 1) 时,它完全无效。
更关键的是:PHP 8.3 默认禁用 mysqlnd 的旧式 escape 行为,某些配置下甚至会抛出弃用警告。依赖它等于在生产环境埋雷。
- 它不区分数据类型,
int字段传入字符串仍会被当作字符串拼接 - 无法防御二阶注入(恶意数据先入库、后续查询再触发)
- 不同数据库的转义规则不一致(如 PostgreSQL 的 dollar-quoting)
PDO::prepare() 必须配合的三项配置
光调用 prepare() 不够,PDO 默认可能启用模拟预处理(PDO::ATTR_EMULATE_PREPARES),这会让参数仍在 PHP 层拼接 SQL,失去防护意义。
初始化 PDO 实例时,必须显式设置:
-
PDO::ATTR_ERRMODE => PDO::ERRMODE_EXCEPTION:让错误立刻抛出,避免静默失败掩盖漏洞 -
PDO::ATTR_EMULATE_PREPARES => false:强制交由数据库原生预处理,确保 SQL 结构与数据彻底分离 -
PDO::ATTR_DEFAULT_FETCH_MODE => PDO::FETCH_ASSOC:减少后续代码中类型混淆风险
示例:
$pdo = new PDO($dsn, $user, $pass, [
PDO::ATTR_ERRMODE => PDO::ERRMODE_EXCEPTION,
PDO::ATTR_EMULATE_PREPARES => false,
PDO::ATTR_DEFAULT_FETCH_MODE => PDO::FETCH_ASSOC
]);
命名参数 vs 问号占位符:选哪个更安全
两者安全性无差别,但可维护性差异很大。命名参数(:username)在复杂查询中明显更可靠。
问号占位符(?)容易因数组顺序错位导致绑定错乱,比如:
$stmt = $pdo->prepare("UPDATE users SET email = ?, status = ? WHERE id = ?");
$stmt->execute([$email, $id, $status]); // ❌ status 被写进 WHERE,id 被当 status
命名参数天然防顺序错误:
$stmt = $pdo->prepare("UPDATE users SET email = :email, status = :status WHERE id = :id");
$stmt->execute([':email' => $email, ':status' => $status, ':id' => $id]); // ✅ 清晰明确
额外建议:对主键、ID 类整数字段,用 bindValue(1, $id, PDO::PARAM_INT) 强制类型,防止字符串型 ID(如 "1 OR 1=1")被意外解析。
动态 WHERE 条件不能拼接 SQL 字符串
常见错误是把条件字段名或操作符(ORDER BY, LIMIT, IN 列表)也当成参数绑定——这是无效的。数据库不允许参数化标识符(列名、表名、关键字)。
正确做法是白名单校验 + 显式映射:
- 字段名必须来自预定义数组:
$allowed_fields = ['name', 'email', 'created_at']; - 排序方向仅接受
ASC/DESC,且需用in_array($dir, ['ASC', 'DESC'], true)校验 - IN 查询的多个值,需用
str_repeat('?,', count($ids) - 1) . '?'动态生成占位符,再逐个绑定
示例(安全的 IN 查询):
$ids = [1, 5, 9];
$placeholders = str_repeat('?,', count($ids) - 1) . '?';
$stmt = $pdo->prepare("SELECT * FROM posts WHERE id IN ($placeholders)");
$stmt->execute($ids);
最易被忽略的一点:预处理语句只保护“值”,不保护“结构”。只要涉及用户可控的 SQL 片段(字段名、表名、函数名、排序逻辑),就必须用白名单硬限制,不能靠任何“看起来像参数”的方式糊弄过去。
php免费学习视频:立即使用
踏上前端学习之旅,开启通往精通之路!从前端基础到项目实战,循序渐进,一步一个脚印,迈向巅峰!











