模糊查询 LIKE 转义函数

解读

国内一线/二线公司面试中,这道题常被放在“SQL 注入防御”或“ORM 安全”环节,用来快速筛掉只会写 "%$keyword%" 的候选人。面试官真正想听的是:

  1. 你知不知道 LIKE 的四个通配符(%_[])在 MySQL 里也会参与语法解析;
  2. 你清不清楚 mysqli_real_escape_string 只能转义“单双斜杠+二进制”,并不会处理 %_
  3. 你是否能给出“手动转义 → 占位符 → 绑定参数”的完整闭环,而不是背一句“用预处理就完事了”。
    答不到这三层,基本会被追问“如果必须用 LIKE 怎么办”,答得好则能顺势展示你对 Laravel、Doctrine、ThinkPHP 等框架源码的熟悉度。

知识点

  1. LIKE 通配符体系:
    • % 任意长度;_ 单个字符;[...] 字符集(MySQL 8 默认关,但面试官会假设开)。
  2. 转义优先级:
    • 服务端语法解析先于参数绑定,因此占位符无法绕过通配符语义。
  3. PHP 侧转义函数:
    • 原生:无官方封装,需手动拼接 ESCAPE 子句;
    • PDO:无内建,需自定义 quoteLike()
    • Laravel:\Illuminate\Support\Str::replaceArray + whereRaw("... LIKE ? ESCAPE '\\'", [$pat])
    • ThinkPHP:Db::raw() 同理。
  4. 性能陷阱:
    • 左通配 %keyword 导致索引失效,应引导面试官到“全文索引”或“ES 异构”话题。
  5. 安全边界:
    • 转义后仍需使用预处理,防止宽字节、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 强制要求左通配,也能阻断通配符注入,同时保留索引右匹配优化空间。”

拓展思考

  1. 左通配性能劣化场景下,可引入 MySQL 8 的 INVISIBLE 列 + 反向索引,或把关键词拆成 n-gram 存入副表,用 FULLTEXT 替代 LIKE。
  2. 在 SaaS 多租户系统里,不同租户可能自定义转义字符,需把 ESCAPE 子句做成占位符,避免拼接:
    WHERE content LIKE ? ESCAPE ?
    
    绑定数组 ['%'.$kw.'%', $tenant->escape_char]
  3. 如果业务层允许正则匹配,优先用 REGEXP_LIKE(content, ?, 'c'),再走 PCRE 预编译缓存,可彻底规避通配符转义问题。