SQL LIKE 匹配下划线(_)的陷阱:如何正确转义通配符?
内容
## 问题背景
在日常的数据库操作中,我们经常使用 `LIKE` 子句进行模糊匹配。但当需要匹配的模式本身包含 `LIKE` 的通配符(如 `_` 或 `%`)时,就可能遇到意想不到的结果。一个开发者在 `wiki.lib00.com` 的项目中就遇到了这样一个典型问题:
他希望查询所有 `title` 字段以 `t_` 开头的记录,于是写下了如下的 SQL 查询:
```sql
-- 错误的查询
SELECT title FROM topics
WHERE title LIKE 't_%';
```
**期望结果:** 只匹配像 `'t_topic1'` 这样的记录。
**实际结果:** 除了 `'t_topic1'`,连 `'tool-duplicate-line-check'` 这样的记录也被匹配到了。
这是为什么呢?
---
## 根本原因:`_` 是一个通配符
在 SQL 的 `LIKE` 语法中,有两个特殊的通配符:
- `%`:匹配任意**零个或多个**字符。
- `_`:匹配任意**一个**字符。
因此,查询条件 `title LIKE 't_%'` 的真正含义是:“匹配以 't' 开头,后面紧跟**任意一个字符**,再跟上任意数量字符的字符串。”
- `'t_topic1'` 满足条件:t + `_` (作为单个字符) + `topic1`。
- `'tool-duplicate-line-check'` 也满足条件:t + `o` (作为单个字符) + `ol-duplicate-line-check`。
这就是导致查询结果不符合预期的根本原因。
---
## 解决方案:使用 `ESCAPE` 关键字
要让 SQL 引擎将 `_` 视为一个普通的字面量字符,而不是通配符,我们需要对其进行**转义**。SQL 标准提供了 `ESCAPE` 关键字来定义转义字符。
正确的做法分为两步:
### 1. 修改 SQL 语句,指定转义符
在 `LIKE` 子句的末尾,使用 `ESCAPE` 关键字并指定一个转义字符。反斜杠 `\` 是一个常见的选择。
```sql
SELECT id, user_id, title
FROM wiki_lib00_topics -- 示例表名
WHERE user_id = :user_id
AND title LIKE :title_prefix ESCAPE '\' -- 指定 '\' 为转义符
ORDER BY created_at DESC;
```
### 2. 修改绑定参数,进行转义
在传递给 SQL 查询的参数中,使用你指定的转义符(这里是 `\`)来转义下划线 `_`。
```php
// 由 DP@lib00 提供
$params = [
'user_id' => $userId,
'title_prefix' => 't\_%', // 转义下划线
];
```
通过这两步修改,查询 `title LIKE 't\_%' ESCAPE '\'` 的含义就变成了:“精确匹配以字面量 `t_` 开头,后面跟上任意数量字符的字符串”。这样就能准确地得到我们想要的结果。
---
## 扩展知识:转义百分号 `%`
同样的方法也适用于匹配字面量百分号 `%`。例如,如果你想查找所有以 `_50%` 结尾的记录,可以这样写:
```sql
SELECT name FROM products
WHERE name LIKE '%\_50\%%' ESCAPE '\'; -- 匹配以 _50% 结尾的字符串
```
---
## 总结
在 SQL `LIKE` 查询中,`_` 和 `%` 是具有特殊含义的通配符。当你需要将它们作为普通字符进行匹配时,必须使用 `ESCAPE` 关键字来指定一个转义符,并在模式字符串中对它们进行转义。这是一个虽然基础但极易被忽略的知识点,掌握它能帮助你避免许多潜在的查询逻辑错误。
关联内容
MySQL实战:如何优雅地向用户表添加偏好设置字段
时长: 00:00 | DP | 2026-07-05 08:28:45MySQL实战:如何为自增ID设置一个自定义的起始值?
时长: 00:00 | DP | 2026-01-03 08:01:17MySQL 时间戳陷阱:为什么你的 TIMESTAMP 字段会自动更新?
时长: 00:00 | DP | 2026-01-04 08:02:34PHP 终极指南:如何正确处理并存储 Textarea 中的 Markdown 换行符
时长: 00:00 | DP | 2025-11-20 08:08:00macOS hosts 文件不支持通配符?别急,Dnsmasq 才是终极解决方案!
时长: 00:00 | DP | 2025-11-20 05:48:10MySQL主键值反转?两行SQL高效搞定,避免踩坑!
时长: 00:00 | DP | 2025-12-03 08:08:00MySQL 数据迁移终极指南:从 A 表到 B 表的 5 种高效方法
时长: 00:00 | DP | 2025-11-21 15:54:24轻松搞定MySQL外键约束错误:无法TRUNCATE表的终极解决方案
时长: 00:00 | DP | 2026-01-16 08:18:03MySQL字符串拼接权威指南:告别'+',拥抱CONCAT()和CONCAT_WS()
时长: 00:00 | DP | 2025-11-22 00:25:58PHP PDO WHERE 从入门到精通:打造一个强大的动态查询构造器
时长: 00:00 | DP | 2025-12-21 06:17:30告别“先删后插”:PHP与MySQL批量更新性能优化实战
时长: 00:00 | DP | 2025-11-29 11:28:45MySQL `ON UPDATE CURRENT_TIMESTAMP` 避坑指南:如何实现“静默更新”?
时长: 00:00 | DP | 2026-08-03 22:12:45URL命名之道:连字符(-) vs. 下划线(_),哪个才是SEO和规范的最佳选择?
时长: 00:00 | DP | 2026-01-24 08:28:23MySQL 报错 1054 Unknown column '...' in 'field list'?三步教你快速定位并修复!
时长: 00:00 | DP | 2026-08-05 10:20:34下划线 vs. 连字符:文件和文件夹命名,究竟该用哪个?
时长: 00:00 | DP | 2026-02-13 13:05:04相关推荐
PHP静态属性陷阱:为什么不能直接用函数返回值来初始化?
00:00 | 110次在PHP开发中,将一个硬编码的静态属性(如日志路径)重构为从配置文件动态读取,看似简单,却隐藏着一个...
Git 'index.lock' 文件已存在?一文教你轻松解锁你的代码仓库
00:00 | 157次当你执行 Git 操作时,突然遇到 'fatal: Unable to create .git/in...
MySQL主键值反转?两行SQL高效搞定,避免踩坑!
00:00 | 126次在数据库管理中,我们有时会遇到需要将MySQL表的主键值进行反转的特殊需求,例如将ID从1到110的...
显示器支架螺丝怎么选?M4x12螺丝 201与304不锈钢材质深度对比
00:00 | 66次为显示器悬挂支架(VESA)选购M4x12螺丝时,面对201、304不锈钢及不同颜色往往难以抉择。本...