预处理语句不绝对安全:仅对参数值转义,无法防护动态sql结构(如表名、字段名);须用白名单校验;pdo::quote()有字符集依赖和类型局限,应慎用;需设errmode为exception并正确回滚事务。

预处理语句不等于绝对安全
很多人以为只要用了 PDO::prepare() 和 execute(),就自动防 SQL 注入。其实不然——如果把用户输入拼接到 SQL 字符串里再准备(比如动态表名、字段名、排序方向),预处理机制根本不起作用。因为预处理只对 参数值 做转义和类型绑定,不处理 SQL 结构本身。
正确做法是:表名、字段名、ORDER BY 子句等必须来自白名单校验,不能由用户直接控制。例如:
$allowed_fields = ['name', 'email', 'created_at'];
$sort_field = $_GET['sort'] ?? 'id';
$sort_dir = in_array($_GET['dir'] ?? 'ASC', ['ASC', 'DESC']) ? $_GET['dir'] : 'ASC';
<p>if (!in_array($sort_field, $allowed_fields)) {
throw new InvalidArgumentException('Invalid sort field');
}</p><p>$sql = "SELECT * FROM users ORDER BY $sort_field $sort_dir";
$stmt = $pdo->query($sql); // 注意:这里不能用 prepare 绑定字段名</p>错误地信任 PDO::quote() 或手动拼接
PDO::quote() 看似能“转义字符串”,但它依赖当前连接的字符集设置,且仅适用于字符串值,不处理整数、布尔或 NULL。更严重的是,它返回的是带引号的字符串字面量(如 'admin'' OR 1=1'),若误用于非字符串上下文(比如数字 ID 比较),反而可能引入逻辑漏洞或语法错误。
应始终优先使用参数化查询;仅在极少数无法用 prepare 的场景(如动态构建 WHERE 条件数组)中,才配合白名单 + quote() 使用,并确保引号和类型完全匹配。
立即学习“PHP免费学习笔记(深入)”;
忽略错误模式与异常处理
默认情况下,PDO 在出错时静默失败(PDO::ATTR_ERRMODE => PDO::ERRMODE_SILENT),开发者可能没检查 $stmt->errorCode() 就继续执行,导致后续逻辑基于空结果运行,掩盖真实问题。
推荐配置为异常模式:
- 初始化 PDO 时加上
PDO::ATTR_ERRMODE => PDO::ERRMODE_EXCEPTION - 避免用
@抑制错误,让异常自然抛出便于定位 - 不要在生产环境显示详细错误信息(如关闭
display_errors),但需记录到日志
事务中混用非预处理操作或忽略回滚
在事务中,如果部分语句用预处理,另一些却用 exec() 拼接执行(比如插入日志或更新计数器),就可能破坏一致性,也埋下注入风险。更常见的是:捕获异常后只打印错误,忘记调用 $pdo->rollback(),导致数据处于中间状态。
标准写法应统一使用预处理,并用 try-catch 显式管理事务边界:
$pdo->beginTransaction();
try {
$stmt1 = $pdo->prepare("INSERT INTO orders (...) VALUES (?, ?, ?)");
$stmt1->execute([$user_id, $amount, $status]);
<pre class='brush:php;toolbar:false;'>$stmt2 = $pdo->prepare("UPDATE accounts SET balance = balance - ? WHERE id = ?");
$stmt2->execute([$amount, $user_id]);
$pdo->commit();} catch (Exception $e) { $pdo->rollback(); throw $e; }











