在使用PHP开发数据库相关功能时,PDO预处理语句是防范SQL注入的核心方案,但在LIKE模糊查询和动态列名这两个特殊场景中,很多开发者会因为使用方式不当留下安全隐患,需要针对性采用正确的处理逻辑。

PDO预处理语句的基本工作原理
PDO预处理语句的核心是将SQL语句的结构和数据参数分离,数据库会先对SQL语句模板进行编译,之后再传入参数值执行,这样参数值就不会被当作SQL指令解析,从根源上避免SQL注入。常规的参数绑定场景只需要使用bindParam或者bindValue方法即可,但LIKE操作和动态列名属于特殊场景,不能直接套用常规方式。
LIKE操作的安全处理方案
LIKE模糊查询的场景中,如果我们直接把通配符%拼接到参数里,或者把整个LIKE条件拼接到SQL语句中,都会破坏预处理语句的参数分离特性,导致注入风险。
错误示例
<?php // 错误做法1:直接拼接通配符到SQL语句中 $keyword = $_GET['keyword']; $sql = "SELECT * FROM users WHERE username LIKE '%$keyword%'"; $stmt = $pdo->prepare($sql); $stmt->execute(); // 错误做法2:参数绑定后拼接通配符 $keyword = $_GET['keyword']; $sql = "SELECT * FROM users WHERE username LIKE ?"; $stmt = $pdo->prepare($sql); $stmt->bindValue(1, "%$keyword%"); // 看似正确但实际仍有风险?不,这个是正确的,下面错误示例是拼接SQL // 错误做法3:拼接参数到SQL $sql = "SELECT * FROM users WHERE username LIKE '%" . $keyword . "%'"; $stmt = $pdo->prepare($sql); $stmt->execute(); ?>
正确示例
<?php // 正确做法:在参数值中拼接通配符,再通过预处理绑定 $keyword = $_GET['keyword'] ?? ''; // 对输入做基础过滤,避免恶意字符 $keyword = trim($keyword); $sql = "SELECT * FROM users WHERE username LIKE ?"; $stmt = $pdo->prepare($sql); // 把通配符和用户输入拼接后作为绑定值,数据库会把整个值当作字符串处理 $stmt->bindValue(1, "%$keyword%", PDO::PARAM_STR); $stmt->execute(); $result = $stmt->fetchAll(PDO::FETCH_ASSOC); ?>
这种处理方式下,用户输入的内容会被当作普通的字符串参数,即使包含SQL特殊字符,也不会被数据库解析为SQL指令,同时通配符%也能正常生效实现模糊查询。
动态列名的安全处理方案
动态列名指的是SQL语句中的列名、表名需要根据用户输入或者其他动态逻辑生成,比如用户可以选择按照username或者email列进行排序,这时候如果直接把动态值拼接到SQL中,就会引发注入风险,因为列名属于SQL结构的一部分,不能通过预处理参数绑定的方式传入。
错误示例
<?php // 错误做法:直接拼接动态列名到SQL $sortColumn = $_GET['sort_column']; $sql = "SELECT * FROM users ORDER BY $sortColumn DESC"; $stmt = $pdo->prepare($sql); $stmt->execute(); ?>
正确示例
动态列名的正确处理方式是使用白名单校验,只允许传入预设的合法列名,拒绝所有不在白名单内的值。
<?php
// 定义允许的动态列名白名单
$allowedColumns = ['username', 'email', 'create_time'];
$sortColumn = $_GET['sort_column'] ?? 'create_time';
// 校验列名是否在白名单中,不在则使用默认列名
if (!in_array($sortColumn, $allowedColumns)) {
$sortColumn = 'create_time';
}
// 白名单中的列名是安全的,可以直接拼接
$sql = "SELECT * FROM users ORDER BY $sortColumn DESC";
$stmt = $pdo->prepare($sql);
$stmt->execute();
$result = $stmt->fetchAll(PDO::FETCH_ASSOC);
?>
因为白名单内的列名都是开发者预先定义的合法值,不存在用户输入的恶意内容,所以直接拼接也不会有注入风险。绝对不能省略白名单校验直接使用用户传入的动态列名,哪怕做了转义也不安全,因为列名转义规则复杂且不同数据库存在差异。
两种场景的注意事项总结
- LIKE操作的参数必须放在预处理绑定的值中,通配符
%和_要和用户输入拼接后作为整体参数传入,不能拼接到SQL模板里。 - 动态列名、表名属于SQL结构,无法通过预处理参数绑定处理,必须通过白名单校验限制允许的值,不允许直接使用用户输入的内容。
- 所有用户输入的数据在用于数据库操作前,都建议做基础的格式校验和过滤,进一步降低安全风险。
- 不要过度依赖
addslashes等转义函数,预处理语句配合正确的使用方式才是防范SQL注入的可靠方案。
常见问题解答
LIKE操作中如果用户输入包含%字符怎么办
如果业务需要支持用户输入%作为普通字符,需要对用户输入中的%和_做转义处理,再拼接通配符。示例代码如下:
<?php $keyword = $_GET['keyword'] ?? ''; // 转义用户输入中的通配符 $keyword = str_replace(['%', '_'], ['\%', '\_'], $keyword); $sql = "SELECT * FROM users WHERE username LIKE ?"; $stmt = $pdo->prepare($sql); $stmt->bindValue(1, "%$keyword%", PDO::PARAM_STR); $stmt->execute(); ?>
动态列名能不能用PDO的quote方法处理
不建议,quote方法主要用于转义字符串参数,对不同数据库的列名转义规则支持不一致,而且列名本身不属于字符串参数的范畴,白名单校验是更通用更可靠的处理方式。