直接拼接$sql一定不安全,因用户输入可闭合引号、注入注释或union;pdo/mysqli预编译仅保护参数值,表名等结构部分须白名单校验。

直接拼接 $sql 为什么一定不安全
只要用户输入(哪怕只是 URL 参数、表单字段、文件名)参与了字符串拼接,就可能被用来闭合引号、注入注释符或 UNION 查询。比如:$sql = "SELECT * FROM users WHERE id = " . $_GET['id'],当传入 1 OR 1=1,实际执行的就是 SELECT * FROM users WHERE id = 1 OR 1=1;若传入 1' UNION SELECT password FROM users--,后果更严重。PDO 或 MySQLi 的预编译机制,是在 SQL 模板解析阶段就固定结构,参数值只作为“数据”传入,数据库绝不会把它们当 SQL 代码执行。
PDO::prepare() 替换拼接的三步实操
把原来类似 $sql = "SELECT * FROM $table WHERE status = '$status' AND created > '$date'"; 的写法,换成以下结构:
- 用占位符(
?或命名参数如:status)代替所有动态值,表名/字段名等**结构部分仍需白名单校验**,不能参数化 - 调用
$pdo->prepare($sql)得到语句对象,这一步发送模板给数据库做语法检查和预编译 - 用
execute()传入参数数组,或用bindParam()/bindValue()绑定变量——此时值不经过字符串拼接,由驱动自动转义并按类型传递
示例:
$sql = "SELECT * FROM orders WHERE status = ? AND created BETWEEN ? AND ?"; $stmt = $pdo->prepare($sql); $stmt->execute([$status, $date_from, $date_to]);
哪些地方不能靠 prepare() 自动兜底
预编译只保护「值」,不保护「结构」。以下写法依然危险,且 prepare() 完全无效:
- 把表名、字段名、ORDER BY 字段、UNION 子句等拼进
$sql字符串——必须用白名单判断后硬编码,例如:in_array($table, ['users', 'orders']) ? $table : 'users' - 在
WHERE中拼接未过滤的条件逻辑,如" AND " . $_GET['extra_where']——应改用键值映射 + 白名单字段 + 参数绑定组合 - 用
implode()拼接多个IN (?)占位符但没同步生成对应数量的参数——要动态生成占位符串,并确保execute()数组长度匹配
MySQLi 用户怎么改
MySQLi 同样支持预处理,流程一致,只是 API 不同:
- 用
$mysqli->prepare($sql)创建语句对象 - 用
$stmt->bind_param("sis", $id, $name, $status)绑定变量,第一个参数是类型字符串(s字符串、i整数、d浮点、b大对象) - 注意:MySQLi 的
bind_param()要求传变量引用,不能直接传字面量,所以得先赋值给变量再绑定
示例:
$sql = "INSERT INTO logs (level, message) VALUES (?, ?)";
$stmt = $mysqli->prepare($sql);
$level = "error";
$message = $_POST['msg']; // 仍需业务层过滤 XSS 等,但 SQL 层已安全
$stmt->bind_param("ss", $level, $message);
$stmt->execute();
最常被忽略的一点:预编译不是魔法开关,它只在「参数完全隔离」的前提下生效。一旦你把用户输入塞进 SQL 字符串里再交给 prepare(),就已经晚了。
php免费学习视频:立即使用
踏上前端学习之旅,开启通往精通之路!从前端基础到项目实战,循序渐进,一步一个脚印,迈向巅峰!











