模糊查询 LIKE 转义函数
解读
国内一线/二线公司面试中,这道题常被放在“SQL 注入防御”或“ORM 安全”环节,用来快速筛掉只会写 "%$keyword%" 的候选人。面试官真正想听的是:
- 你知不知道 LIKE 的四个通配符(
%、_、[、])在 MySQL 里也会参与语法解析; - 你清不清楚
mysqli_real_escape_string只能转义“单双斜杠+二进制”,并不会处理%和_; - 你是否能给出“手动转义 → 占位符 → 绑定参数”的完整闭环,而不是背一句“用预处理就完事了”。
答不到这三层,基本会被追问“如果必须用 LIKE 怎么办”,答得好则能顺势展示你对 Laravel、Doctrine、ThinkPHP 等框架源码的熟悉度。
知识点
- LIKE 通配符体系:
%任意长度;_单个字符;[...]字符集(MySQL 8 默认关,但面试官会假设开)。
- 转义优先级:
- 服务端语法解析先于参数绑定,因此占位符无法绕过通配符语义。
- PHP 侧转义函数:
- 原生:无官方封装,需手动拼接
ESCAPE子句; - PDO:无内建,需自定义
quoteLike(); - Laravel:
\Illuminate\Support\Str::replaceArray+whereRaw("... LIKE ? ESCAPE '\\'", [$pat]); - ThinkPHP:
Db::raw()同理。
- 原生:无官方封装,需手动拼接
- 性能陷阱:
- 左通配
%keyword导致索引失效,应引导面试官到“全文索引”或“ES 异构”话题。
- 左通配
- 安全边界:
- 转义后仍需使用预处理,防止宽字节、SET NAMES 绕过。
答案
“我会分三步做:
第一步,定义一个通用转义函数,把 %、_、[、] 前统一加反斜杠,并指定单字节转义字符,避免多字节注入:
function escapeLike(string $raw, string $escapeChar = '\\'): string
{
return str_replace(
[$escapeChar, '%', '_', '[', ']'],
[$escapeChar.$escapeChar, $escapeChar.'%', $escapeChar.'_', $escapeChar.'[', $escapeChar.']'],
$raw
);
}
第二步,在 SQL 里显式声明 ESCAPE 子句,确保 MySQL 解析层使用同一转义字符:
$keyword = escapeLike($_GET['q']);
$sql = "SELECT id,title FROM post WHERE title LIKE ? ESCAPE '\\'";
$stmt = $pdo->prepare($sql);
$stmt->execute(['%'.$keyword.'%']);
第三步,如果项目用 Laravel,我会封装到全局作用域,保证链式调用也安全:
Builder::macro('whereLike', function ($field, $text) {
$text = addcslashes($text, '\\%_[]');
return $this->whereRaw("$field LIKE ? ESCAPE '\\'", ['%'.$text.'%']);
});
这样即使 DBA 强制要求左通配,也能阻断通配符注入,同时保留索引右匹配优化空间。”
拓展思考
- 左通配性能劣化场景下,可引入 MySQL 8 的
INVISIBLE列 + 反向索引,或把关键词拆成 n-gram 存入副表,用FULLTEXT替代 LIKE。 - 在 SaaS 多租户系统里,不同租户可能自定义转义字符,需把
ESCAPE子句做成占位符,避免拼接:
绑定数组WHERE content LIKE ? ESCAPE ?['%'.$kw.'%', $tenant->escape_char]。 - 如果业务层允许正则匹配,优先用
REGEXP_LIKE(content, ?, 'c'),再走 PCRE 预编译缓存,可彻底规避通配符转义问题。