预处理语句是防止sql注入最有效方式,核心是sql结构与数据分离;pdo需禁用模拟预处理,mysqli需注意bind_param类型和引用传递;表名、字段名等不可参数化,须白名单校验。

PHP 使用预处理语句是防止 SQL 注入最有效、最标准的方式。核心在于:**把 SQL 结构和数据完全分离**——SQL 语句模板先编译,参数后安全绑定,数据库不会把传入的值当作代码执行。
为什么预处理能防注入?
因为预处理语句在底层使用数据库的“参数化查询”机制。例如你写 SELECT * FROM users WHERE name = ?,问号只是占位符,后续传入的字符串(哪怕含 ' OR 1=1 --)都会被当作纯文本值处理,不会拼进 SQL 语法树里。数据库驱动自动做转义和类型约束,开发者无需手动 addslashes() 或 htmlspecialchars() 处理 SQL 场景。
PDO 方式:推荐用于新项目
PDO 支持多种数据库,接口统一,且默认开启预处理模拟(需确认关闭 emulated prepares 才真正交由数据库处理):
- 连接时设置
PDO::ATTR_EMULATE_PREPARES => false,确保走原生预处理 - 用
prepare()定义语句,execute()绑定参数(支持数组或逐个 bindValue) - 占位符可用
?(位置式)或:name(命名式),后者更易读、可复用
示例:
立即学习“PHP免费学习笔记(深入)”;
php$pdo = new PDO("mysql:host=localhost;dbname=test", $user, $pass, [
PDO::ATTR_EMULATE_PREPARES => false,
PDO::ATTR_ERRMODE => PDO::ERRMODE_EXCEPTION
]);
$stmt = $pdo->prepare("SELECT * FROM users WHERE status = :status AND age > ?");
$stmt->execute(['status' => 'active', 18]);
$results = $stmt->fetchAll();
?>
MySQLi 方式:适合只用 MySQL 的场景
支持面向对象和过程两种风格,原生预处理同样可靠:
- 用
prepare()创建语句对象,bind_param()绑定变量(注意类型字符:i 整型、s 字符串、d 浮点、b 大对象) - 所有参数必须是变量(不能是表达式或字面量),且
bind_param()要求传引用,常用call_user_func_array()动态绑定 - 查询后需调用
get_result()或bind_result()取数据
示例(面向对象):
$mysqli = new mysqli("localhost", $user, $pass, "test");$stmt = $mysqli->prepare("INSERT INTO logs (ip, action) VALUES (?, ?)");
$ip = $_SERVER['REMOTE_ADDR'];
$action = "login";
$stmt->bind_param("ss", $ip, $action);
$stmt->execute();
?>
常见误区与注意事项
- 不要混用预处理和字符串拼接——比如
"WHERE id = " . $_GET['id']即使后面用了 prepare 也已失效 - 表名、字段名、排序方向(ASC/DESC)等无法参数化,必须白名单校验或硬编码,不可来自用户输入
- LIKE 查询中通配符(%、_)要放在 PHP 变量里,而不是 SQL 拼进去:
WHERE name LIKE CONCAT('%', ?, '%') - 批量插入可用多值占位符,但需动态生成对应数量的
?,再一次性 bind











