SQL LIKE 模糊搜索特殊字符转义实战指南

 更新时间:2026年06月25日 09:08:52   作者:霸道流氓气质  
本文详细讲解了在使用SQL LIKE操作符进行模糊搜索时如何处理特殊字符如%、%、\等问题,介绍了Java层转义、SQL层REPLACE函数及ESCAPE关键字等三种解决方案,并提供了完整示例和最佳实践,帮助开发者避免搜索逻辑异常

在使用 SQL 的 LIKE 操作符进行模糊搜索时,如果你要搜索的字符串包含特殊字符,比如 %_ 或 \(在某些数据库系统中,例如 MySQL),你需要对这些特殊字符进行转义,否则它们会被解释为通配符或特殊语法的一部分,这可能导致你的查询无法按预期工作。

先说下为什么需要转义:

LIKE中有两个核心通配符:

通配符含义
%匹配任意数量的字符(含零个)
_匹配单个字符

问题示例:想搜索用户名包含 _(下划线)的用户,写成 WHERE name LIKE '%_%',结果会匹配所有至少包含一个任意字符的记录,而非包含下划线的记录。必须对 _ 进行转义。

一、问题描述

在使用 SQL LIKE 进行模糊搜索时,%_\ 是特殊字符:

  • % — 匹配任意数量的任意字符
  • _ — 匹配单个任意字符
  • \ — 转义字符

如果用户在搜索框中输入了这些字符(如搜索 100%user_name),不做处理会导致搜索逻辑异常。

二、问题演示

2.1 正常搜索

-- 搜索包含"张三"的记录
SELECT * FROM employee WHERE name LIKE '%张三%';
-- 正确匹配:张三、张三丰、大张三

2.2 异常场景

-- 用户输入 "100%" 搜索,期望搜索包含"100%"字面量的记录
-- 实际 SQL:
SELECT * FROM employee WHERE name LIKE '%100%%';
-- 等价于 LIKE '%100%'(最后的 % 被当作通配符),匹配所有包含"100"的记录
-- 用户输入 "___" 搜索
-- 实际 SQL:
SELECT * FROM employee WHERE name LIKE '%___%';
-- _ 是通配符,匹配任意3个字符,等价于搜索长度≥3的所有记录
-- 用户输入 "%%%" 搜索
-- 实际 SQL:
SELECT * FROM employee WHERE name LIKE '%%%%%';
-- 等价于 LIKE '%',匹配所有记录,搜索条件完全失效

三、解决方案

3.1 方案一:Java 层转义(推荐)

在传入 SQL 之前,对用户输入进行预处理:

/**
 * 转义LIKE查询中的特殊字符.
 *
 * @param param 用户原始输入
 * @return 转义后的安全字符串
 */
private String escapeLikeParam(String param) {
    if (param == null) {
        return null;
    }
    return param
        .replace("\\", "\\\\")  // 先转义反斜杠本身
        .replace("%", "\\%")    // 再转义 %
        .replace("_", "\\_");   // 再转义 _
}

使用方式:

@Override
public PageInfo<EmployeeDto> listEmployee(QueryParamsDto paramsDto) {
    // 在查询前转义模糊搜索参数
    if (StringUtils.isNotBlank(paramsDto.getName())) {
        paramsDto.setName(escapeLikeParam(paramsDto.getName()));
    }
    if (StringUtils.isNotBlank(paramsDto.getCode())) {
        paramsDto.setCode(escapeLikeParam(paramsDto.getCode()));
    }
    return employeeMapper.listEmployee(paramsDto);
}

SQL 保持不变:

<if test="param.name != null and param.name != ''">
    AND e.name LIKE CONCAT('%', #{param.name}, '%')
</if>

3.2 方案二:SQL 层转义

直接在 MyBatis XML 中使用 REPLACE 函数:

<if test="param.name != null and param.name != ''">
    AND e.name LIKE CONCAT('%',
        REPLACE(REPLACE(REPLACE(#{param.name}, '\\', '\\\\'), '%', '\\%'), '_', '\\_'),
        '%')
</if>

3.3 方案三:SQL ESCAPE 子句

MySQL 支持 ESCAPE 关键字指定自定义转义字符:

<if test="param.name != null and param.name != ''">
    AND e.name LIKE CONCAT('%', #{param.name}, '%') ESCAPE '/'
</if>

Java 中用 / 作为转义符:

private String escapeLikeParam(String param) {
    if (param == null) return null;
    return param
        .replace("/", "//")
        .replace("%", "/%")
        .replace("_", "/_");
}

3.4 方案对比

方案优点缺点适用场景
Java 层转义简单直观,不改 SQL需要每个搜索字段都处理通用推荐
SQL REPLACE不改 Java 代码SQL 可读性差不方便改 Java 时
ESCAPE 子句标准 SQL 语法需要统一转义字符严格规范的项目

四、完整示例

4.1 Service 层

@Slf4j
@Service
public class EmployeeServiceImpl implements EmployeeService {
    @Resource
    private EmployeeMapper employeeMapper;
    @Override
    public PageInfo<EmployeeDto> listEmployee(EmployeeQueryDto queryDto) {
        // 模糊搜索参数转义
        if (StringUtils.isNotBlank(queryDto.getName())) {
            queryDto.setName(escapeLikeParam(queryDto.getName()));
        }
        if (StringUtils.isNotBlank(queryDto.getPhone())) {
            queryDto.setPhone(escapeLikeParam(queryDto.getPhone()));
        }
        if (StringUtils.isNotBlank(queryDto.getEmail())) {
            queryDto.setEmail(escapeLikeParam(queryDto.getEmail()));
        }
        List<EmployeeDto> list = employeeMapper.listEmployee(queryDto);
        return new PageInfo<>(list);
    }
    /**
     * 转义LIKE查询中的特殊字符.
     */
    private String escapeLikeParam(String param) {
        if (param == null) {
            return null;
        }
        return param
            .replace("\\", "\\\\")
            .replace("%", "\\%")
            .replace("_", "\\_");
    }
}

4.2 Mapper XML

<select id="listEmployee" resultType="com.example.dto.EmployeeDto">
    SELECT id, name, phone, email, dept_code
    FROM employee
    <where>
        <if test="param.name != null and param.name != ''">
            AND name LIKE CONCAT('%', #{param.name}, '%')
        </if>
        <if test="param.phone != null and param.phone != ''">
            AND phone LIKE CONCAT('%', #{param.phone}, '%')
        </if>
        <if test="param.email != null and param.email != ''">
            AND email LIKE CONCAT('%', #{param.email}, '%')
        </if>
    </where>
    ORDER BY id DESC
</select>

4.3 转义效果对照

用户输入转义后实际 SQL LIKE匹配行为
张三张三'%张三%'正常模糊搜索
100%100\%'%100\%%'搜索包含"100%"字面量
user_nameuser\_name'%user\_name%'搜索包含"user_name"字面量
%%%\%\%\%'%\%\%\%%'搜索包含"%%%"字面量
a\ba\\b'%a\\b%'搜索包含"a\b"字面量

五、注意事项

5.1 转义顺序很重要

// 正确顺序:先转义反斜杠,再转义 % 和 _
param.replace("\\", "\\\\")  // 第一步
    .replace("%", "\\%")      // 第二步
    .replace("_", "\\_");     // 第三步
// 错误顺序:如果先转义 %,后转义 \
// "100%" → "100\%" → "100\\%" (反斜杠被二次转义,结果错误)

5.2 #{} vs ${}

<!-- 安全:#{} 使用预编译,防止 SQL 注入 -->
AND name LIKE CONCAT('%', #{param.name}, '%')
<!-- 危险:${} 直接拼接,存在 SQL 注入风险 -->
AND name LIKE '%${param.name}%'

#{} 已经防止了 SQL 注入,但不防止 LIKE 通配符问题。两者是不同层面的安全问题:

  • SQL 注入:通过 #{} 解决(预编译参数化)
  • LIKE 通配符:通过转义解决(本文内容)

5.3 前端 vs 后端处理

处理位置优点缺点
前端转义减少后端逻辑不可靠(可绕过)
后端转义安全可靠需要每个字段处理

建议:始终在后端转义,前端可以做提示但不能依赖。

5.4 空字符串判断

// 转义前先判空,空字符串不需要转义
if (StringUtils.isNotBlank(queryDto.getName())) {
    queryDto.setName(escapeLikeParam(queryDto.getName()));
}

如果不判空,空字符串转义后还是空字符串,虽然不会报错,但多一次无意义的处理。

六、工具类封装

如果项目中多处需要模糊搜索转义,建议封装为工具类:

public final class SqlLikeUtil {
    private SqlLikeUtil() {
    }
    /**
     * 转义 SQL LIKE 查询中的特殊字符(%、_、\).
     *
     * @param param 原始输入
     * @return 转义后的字符串,null 输入返回 null
     */
    public static String escape(String param) {
        if (param == null) {
            return null;
        }
        return param
            .replace("\\", "\\\\")
            .replace("%", "\\%")
            .replace("_", "\\_");
    }
    /**
     * 判断字符串是否包含 LIKE 特殊字符.
     */
    public static boolean containsSpecialChar(String param) {
        if (param == null) {
            return false;
        }
        return param.contains("%") || param.contains("_") || param.contains("\\");
    }
}

使用:

if (StringUtils.isNotBlank(queryDto.getName())) {
    queryDto.setName(SqlLikeUtil.escape(queryDto.getName()));
}

七、最佳实践清单

  1. 所有模糊搜索字段都要转义:不要遗漏任何一个 LIKE 查询的入参
  2. 转义在后端 Service 层做:不依赖前端处理
  3. 转义顺序:先 \ 再 % 再 _:避免二次转义
  4. 使用 CONCAT + #{}:既防注入又支持转义
  5. 不要用 ${} 拼接 LIKE:存在 SQL 注入风险
  6. 空值判断在转义前:避免 NPE
  7. 封装工具类复用:多个模块统一使用
  8. 单元测试覆盖特殊字符:测试 %_\%%、空字符串等场景

八、常见错误与排查

错误现象可能原因解决方案
LIKE '%_%' 匹配了所有记录下划线未被转义,被当作单字符通配符使用 LIKE '%#_%' ESCAPE '#'
LIKE '%\%%' 在 MySQL 中仍匹配所有MySQL 需要双反斜杠 \\%使用 LIKE '%#%%' ESCAPE '#'
转义字符本身无法匹配未对转义字符自身转义连续写两次,如 ## 匹配 #
参数化查询后仍匹配错误参数化不处理通配符语义仍需在应用层对 % 和 _ 转义

九、总结

处理 LIKE 特殊字符转义的核心原则:

  • % 和 _ 必须转义才能作为普通字符匹配
  • 优先使用 ESCAPE 子句,兼容性最好
  • 转义字符本身需连续写两次来匹配
  • 转义 + 参数化 双管齐下,既保正确又保安全
  • 注意 MySQL 的双重转义陷阱

掌握以上方法,即可在各种数据库场景中游刃有余地处理 LIKE 模糊搜索的特殊字符问题。

到此这篇关于SQL LIKE 模糊搜索特殊字符转义实战指南的文章就介绍到这了,更多相关sql like模糊搜索内容请搜索脚本之家以前的文章或继续浏览下面的相关文章希望大家以后多多支持脚本之家!

相关文章

最新评论