SQL LIKE 模糊搜索特殊字符转义实战指南
在使用 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_name | user\_name | '%user\_name%' | 搜索包含"user_name"字面量 |
%%% | \%\%\% | '%\%\%\%%' | 搜索包含"%%%"字面量 |
a\b | a\\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()));
}七、最佳实践清单
- 所有模糊搜索字段都要转义:不要遗漏任何一个 LIKE 查询的入参
- 转义在后端 Service 层做:不依赖前端处理
- 转义顺序:先 \ 再 % 再 _:避免二次转义
- 使用 CONCAT + #{}:既防注入又支持转义
- 不要用 ${} 拼接 LIKE:存在 SQL 注入风险
- 空值判断在转义前:避免 NPE
- 封装工具类复用:多个模块统一使用
- 单元测试覆盖特殊字符:测试
%、_、\、%%、空字符串等场景
八、常见错误与排查
| 错误现象 | 可能原因 | 解决方案 |
|---|---|---|
LIKE '%_%' 匹配了所有记录 | 下划线未被转义,被当作单字符通配符 | 使用 LIKE '%#_%' ESCAPE '#' |
LIKE '%\%%' 在 MySQL 中仍匹配所有 | MySQL 需要双反斜杠 \\% | 使用 LIKE '%#%%' ESCAPE '#' |
| 转义字符本身无法匹配 | 未对转义字符自身转义 | 连续写两次,如 ## 匹配 # |
| 参数化查询后仍匹配错误 | 参数化不处理通配符语义 | 仍需在应用层对 % 和 _ 转义 |
九、总结
处理 LIKE 特殊字符转义的核心原则:
%和_必须转义才能作为普通字符匹配- 优先使用
ESCAPE子句,兼容性最好 - 转义字符本身需连续写两次来匹配
- 转义 + 参数化 双管齐下,既保正确又保安全
- 注意 MySQL 的双重转义陷阱
掌握以上方法,即可在各种数据库场景中游刃有余地处理 LIKE 模糊搜索的特殊字符问题。
到此这篇关于SQL LIKE 模糊搜索特殊字符转义实战指南的文章就介绍到这了,更多相关sql like模糊搜索内容请搜索脚本之家以前的文章或继续浏览下面的相关文章希望大家以后多多支持脚本之家!
相关文章
SQL Server新特性SequenceNumber用法介绍
这篇文章介绍了SQL Server新特性SequenceNumber的用法,对大家的学习或者工作具有一定的参考学习价值,需要的朋友们下面随着小编来一起学习学习吧2022-02-02
SQLServer中NEWID()函数用于生成一个唯一的标识符的方法实践
NEWID函数用于生成一个唯一的标识符,本文主要介绍了SQLServer中NEWID()函数用于生成一个唯一的标识符的方法实践,具有一定的参考价值,感兴趣的可以了解一下2024-08-08
将ACCESS数据库迁移到SQLSERVER数据库两种方法(图文详解)
这篇文章介绍了ACCESS数据库迁移到SQLSERVER数据库两种方法,有需要的朋友可以参考一下2013-10-10


最新评论