在日常的数据库查询中,我们经常需要使用 LIKE 操作符进行模糊匹配。当表名或字段名本身包含下划线(_)时,一个隐蔽而常见的错误就可能悄悄出现——下划线在 SQL 的 LIKE 模式中是一个通配符,代表任意一个字符。如果忘记转义,就会匹配出意料之外的结果。
本文将以一个真实案例为引,深入讲解下划线通配符的原理、错误原因,并提供多种解决方案。
一、一个让人困惑的查询
某天,你想查询数据库中所有以 z_log_ 开头的表(例如 z_log_2023、z_log_archive)。于是写下如下 SQL:
1 | SELECT TABLE_NAME |
你期望的结果只有 z_log_xxx 这样的表。然而,查询结果中却出现了 z_login_records、z_logistics 等表。
为什么会这样? 因为下划线 _ 在 LIKE 模式中是单字符通配符,它匹配任意一个字符(字母、数字、下划线本身除外,后面会讲)。所以 'z_log_%' 的实际含义是:
z- 字母 z_- 任意一个字符(例如l、o、i等)log- 字母 l, o, g_- 又是一个任意字符%- 任意多个字符
因此,z_log_% 会匹配:
z_log_2023✅(第一个下划线匹配_本身?等一下,仔细分析:z_log_2023中:z,然后第一个通配符_匹配_(下划线字符本身也是“任意一个字符”),接着log匹配log,然后第二个通配符_匹配_?不,实际上z_log_2023中,在z之后紧接着是_(下划线字符),这个下划线字符正好被第一个通配符_匹配了;然后log匹配log;第二个通配符_匹配_;最后%匹配2023。所以z_log_2023匹配成功。)z_log_archive✅z_login✅(z+_(通配符匹配l) +log+in? 等等z_login:z,通配符1匹配_?不,z_login中z后是_?不对,是z后直接l,因为z_login没有下划线。让我们重新分析:z_login字符串:位置1z,位置2l,位置3o,位置4g,位置5i,位置6n。模式z_log_%:z-> 匹配z;第一个通配符_-> 匹配任意一个字符,它匹配了l;然后log-> 匹配o、g?但模式中log是三个连续字母,而字符串在l之后是o、g,正好匹配log;接着第二个通配符_-> 匹配i;最后%匹配n。所以z_login也被匹配了!这就是问题根源。)
简单总结:z_log_% 会匹配任何 以 z + 任意字符 + log + 任意字符 开头 的表名。
二、SQL LIKE 通配符速查
| 通配符 | 含义 | 示例 |
|---|---|---|
% |
任意多个字符(包括零个) | 'a%' 匹配以 a 开头的任意字符串 |
_ |
恰好一个任意字符 | 'a_' 匹配 ab、a1,但不匹配 a 或 abc |
[ ] |
指定范围内的一个字符(仅 SQL Server 等支持) | 不通用,此处不展开 |
[^] |
不在范围内的一个字符 | 同上 |
由此可见,下划线 _ 是一个极易被忽视的通配符。
三、正确匹配含下划线的字符串
要匹配 字面意义上的下划线,必须对 _ 进行转义。SQL 标准使用 ESCAPE 子句定义转义字符,但不同数据库的默认转义行为略有差异。
方法一:使用反斜杠转义(MySQL、MariaDB)
在 MySQL 中,默认的转义字符就是反斜杠 \。因此可以直接写:
1 | WHERE TABLE_NAME LIKE 'z\_log\_%' |
注意:在普通字符串中,反斜杠也可能被解释为转义,但 MySQL 中 LIKE 模式会识别 \_ 为字面下划线。你也可以显式指定 ESCAPE 字符,提高可读性:
1 | WHERE TABLE_NAME LIKE 'z$_log$_%' ESCAPE '$' |
这里 $ 被定义为转义字符,$_ 表示字面下划线。
方法二:使用方括号转义(SQL Server、Sybase)
SQL Server 支持用方括号将通配符括起来表示字面值:
1 | WHERE TABLE_NAME LIKE 'z[_]log[_]%' |
这种方法不需要 ESCAPE 子句,非常直观。
方法三:使用 ESCAPE 子句(SQL 标准,所有数据库均支持)
这是最通用、最推荐的做法:
1 | SELECT TABLE_NAME |
ESCAPE 后面跟的字符(这里用 $,也可以用任何未在模式中出现的字符,如 #、@ 等)将指示紧随其后的下划线被当作普通字符处理。
四、不同数据库的转义差异对比
| 数据库 | 默认转义字符 | 推荐方法 | 示例 |
|---|---|---|---|
| MySQL | \ |
使用 \_ 或 ESCAPE |
LIKE 'z\_log\_%' 或 LIKE 'z#_log#_%' ESCAPE '#' |
| PostgreSQL | 无默认 | 必须用 ESCAPE |
LIKE 'z$_log$_%' ESCAPE '$' |
| SQL Server | 无默认 | 方括号 [_] 或 ESCAPE |
LIKE 'z[_]log[_]%' |
| Oracle | 无默认 | ESCAPE |
LIKE 'z$_log$_%' ESCAPE '$' |
| SQLite | 无默认 | ESCAPE |
同 PostgreSQL |
最佳实践:为了代码的可移植性,始终使用 ESCAPE 子句显式指定转义字符。
五、更优雅的替代方案
如果你的数据库支持正则表达式(如 MySQL 的 REGEXP、PostgreSQL 的 SIMILAR TO 或 ~),可以用正则表达式来避免通配符歧义。
MySQL 示例:
1 | WHERE TABLE_NAME REGEXP '^z_log_' |
正则表达式中的下划线没有特殊含义,无需转义。但要注意正则表达式的性能通常低于 LIKE。
PostgreSQL 示例:
1 | WHERE TABLE_NAME ~ '^z_log_' |
六、完整示例:查询以 z_log_ 开头的表
结合上述知识,正确且健壮的查询语句应为:
1 | -- 使用 ESCAPE 子句(所有数据库通用) |
如果只需要 MySQL,也可以简写为:
1 | SELECT TABLE_NAME |
七、常见误区与注意事项
- 下划线本身也是“任意一个字符”:
LIKE '_'会匹配任何长度为1的字符串,包括单个下划线字符。 - 转义字符本身也需要转义:如果要匹配
%或_并且你的转义字符是\,那么\\表示字面反斜杠,\%表示字面百分号。 ESCAPE子句的转义字符只对紧随其后的一个字符生效:例如ESCAPE '$'时,$%表示字面百分号,$_表示字面下划线。INFORMATION_SCHEMA中的表名大小写敏感:不同文件系统会影响大小写匹配,建议使用LIKE时注意大小写。
八、总结
下划线 _ 在 SQL 的 LIKE 模式中是单字符通配符,这是很多人踩过的“坑”。当你要匹配包含下划线的字符串时,切记转义。推荐使用 ESCAPE 子句显式指定转义字符,这样既清晰又兼容所有主流数据库。
| 需求 | 错误写法 | 正确写法 |
|---|---|---|
匹配 z_log_ 开头的表 |
'z_log_%' |
'z$_log$_%' ESCAPE '$' |
| 匹配包含下划线的任意字符串 | '%_%' |
'%$_%' ESCAPE '$' |
匹配名为 a_b 的列 |
WHERE col = 'a_b' |
等号没问题,LIKE 才需转义 |
记住这个小知识,你将避免一次令人困惑的 debug 经历。下次再遇到诡异的 LIKE 结果,请首先检查:你的下划线,转义了吗?