☰
MySQL通配符与LIKE查询:从基础语法到性能优化实战
2026/10/6 3:56:31 网站建设 项目流程

1. 通配符到底是什么:先搞清楚这两个符号

1.1 为什么需要通配符

做开发这些年,我接到过不少类似的需求:"查一下所有姓张的用户""找出所有邮箱是QQ的订单""统计所有手机号以139开头的人"。如果你的第一反应是写一堆WHERE name = '张三'这种精确匹配,那遇到这种"模糊条件"就会非常痛苦。因为你根本不知道用户到底叫张三还是张四,也不可能把每个可能的值都穷举出来。

这时候就需要模糊匹配,而实现模糊匹配最基础的工具就是通配符。MySQL里的通配符主要指%和_两个,配合LIKE关键字一起使用。理解这两者的区别,基本就掌握了MySQL模糊查询的核心。

我把通配符理解成"占位符"或者"填空规则"。%代表"这里可以有任意多个字符,包括零个",_代表"这里必须有且只有一个字符"。

1.2 % 和 _ 的底层逻辑

先看一段最简单的示例。假设我们有一张用户表users,里面存了一些姓名数据:

idnamephone
1张三13812340001
2张三丰13912340002
3张伟13712340003
4李四15812340004
5王张13612340005

执行下面两条SQL,结果完全不同:

SELECT * FROM users WHERE name LIKE '张%'; -- 结果:张三、张三丰、张伟 SELECT * FROM users WHERE name LIKE '张_'; -- 结果:张三、张伟

第一条查到的是所有以"张"开头的名字,后面不管还有几个字都能匹配上;第二条只查到"张"后面有且只有一个字的名字,"张三丰"因为"张"后有两个字,就不符合_的规则。

生活化的类比:%就像你去百度搜索时输入的关键词,百度会返回包含这个词的所有页面,前后可能有各种内容;_则更像是填字游戏里一个确定的格子,只能填一个字。

这两个符号还可以组合使用。'_张%'匹配第二个字是"张"的所有字符串,比如"王张"、"一张纸";'%张_'匹配倒数第二个字是"张"的字符串,比如"张伟"就不符合,因为"张"是第一个字,"伟"是最后一个字,%张_要求"张"前面可以有任意内容,但"张"后面必须还有一个字。

记住一个核心规则:%能匹配零个字符,_必须匹配一个字符。这个区别在实际工作中经常导致数据查不到或者多查出来。

2. LIKE语句的完整用法与实战细节

2.1 四种基本匹配形态

LIKE配合通配符,可以组合出几种最常见的查询场景。我来逐一拆解,这些在实际工作中用到频率非常高:

后缀匹配:查询以指定内容结尾的数据

SELECT * FROM users WHERE email LIKE '%@qq.com';

这个查询会找出所有QQ邮箱。注意%放在前面,表示"@qq.com"前面可以是任意内容。

前缀匹配:查询以指定内容开头的数据

SELECT * FROM users WHERE phone LIKE '139%';

找出所有139号段的手机号。%放在最后,表示"139"后面可以是任意数字。

包含匹配:查询包含指定内容的数据

SELECT * FROM users WHERE name LIKE '%张%';

找出所有名字中带"张"的用户,不管"张"在名字的哪个位置。

精确位数匹配:查询固定长度的数据

SELECT * FROM users WHERE phone LIKE '138________'; -- 8个下划线

这是一条比较妙但容易踩坑的用法。手机号是11位,138后面正好还有8位数字,用8个_加起来正好是11位。这种写法可以匹配所有以138开头的11位手机号,从效果上看和LIKE '138%'一致,但逻辑完全不同——前者对长度有强约束,后者对长度没有约束。如果真的想校验位数,需要用CHAR_LENGTH(phone) = 11配合使用,单独靠_去数位数很容易数错。

2.2 通配符与ESCAPE转义:特殊字符查询怎么破

这是很多新手栽跟头的地方。如果你想在数据库里查一个真的含有%或_字符的数据,比如产品名称是"折扣50%",直接写LIKE '%50%%',MySQL会怎么理解?

它会把第一个和最后一个%当通配符,中间的%也是通配符,结果就是匹配所有"包含50,且50后面可以有任意内容"的数据,根本不是你想要的。

解决办法是使用ESCAPE关键字指定转义符。MySQL默认用反斜杠\作为转义符,但也可以显式指定其他字符:

-- 查询包含"50%"的产品,默认反斜杠转义 SELECT * FROM products WHERE name LIKE '%50\%%'; -- 也可以用ESCAPE显式指定转义符 SELECT * FROM products WHERE name LIKE '%50!%%' ESCAPE '!';

第二条SQL的关键在于ESCAPE '!',它告诉MySQL:!后面的字符不再当作通配符处理。这里的第一个%和最后一个%依然是通配符,中间的!%才表示一个字面量的%。

我一直建议团队里统一用ESCAPE显式指定转义符,而不是依赖默认的反斜杠。原因很现实:在不同系统、不同客户端里,反斜杠本身还牵扯到字符串解析,容易出幺蛾子。显式定义一个符号,逻辑最清晰。

2.3 大小写、排序规则与通配符匹配的坑

MySQL的通配符匹配是否区分大小写,完全取决于表的排序规则(collation)。这是我在实际工作中排查过的一个经典问题。

在MySQL 8.0中,默认排序规则是utf8mb4_0900_ai_ci,这个排序规则不区分大小写。也就是说:

SELECT * FROM users WHERE name LIKE 'abc%';

会把ABC、Abc、aBc都查出来。如果你的业务要求区分大小写,需要在建表时指定utf8mb4_bin排序规则,或者在建字段级别指定:

CREATE TABLE users ( name VARCHAR(50) COLLATE utf8mb4_bin );

utf8mb4_bin会按二进制比较,'abc'和'ABC'就是完全不同的两个字符串。

还有一个容易忽略的细节:如果字段本身有COLLATE指定,但查询条件里用了另一个排序规则,MySQL会报Illegal mix of collations的错误。这个时候就需要在查询里显式加COLLATE:

SELECT * FROM users WHERE name LIKE 'ABC%' COLLATE utf8mb4_bin;

实际项目中这个错误不算高频,但一旦遇到,不熟悉的人容易被错误信息吓住。其实原理很简单:MySQL要求比较的双方排序规则必须兼容。

2.4 通配符与大小写、空格、引号的协同问题

处理用户输入时,查询条件里常常带着首尾空格。我见过不少同事直接写:

SELECT * FROM users WHERE name LIKE CONCAT('%', ' 张三 ', '%');

这样大概率什么都查不到。因为LIKE不会自动去掉字符串两边的空格,空格会被当作匹配内容的一部分。稳妥的做法是先用TRIM处理输入,再拼接到LIKE条件里:

SELECT * FROM users WHERE name LIKE CONCAT('%', TRIM(' 张三 '), '%');

还有一个关于引号的问题。在SQL里,字符串用单引号包裹。如果搜索内容本身就包含单引号,比如一个人名是 "O'Brien",直接拼接会有问题:

-- 错误的写法 SELECT * FROM users WHERE name LIKE '%O'Brien%'; -- 正确的写法:单引号翻倍转义 SELECT * FROM users WHERE name LIKE '%O''Brien%';

这个细节在拼接动态SQL时尤其重要,处理不当不仅查不到数据,还可能导致SQL语法错误。

3. 通配符的性能陷阱:为什么你的查询越来越慢

3.1 前缀通配符与索引失效的真相

做MySQL性能调优时,我几乎每次都会强调一个观点:通配符不是不能用,但要用得聪明。最经典的问题就是%放在开头导致索引失效。

MySQL的B+树索引是按顺序排列的。你可以把索引想象成一本按拼音排序的字典。你想查找所有以"张"开头的词,直接翻到"张"那一页就行,这就是"前缀匹配可以走索引"的原因,对应LIKE '张%'。

但如果你要查所有包含"张"的词,比如LIKE '%张%',字典按拼音排序的优势就完全用不上了。你只能把整本字典从头到尾翻一遍,看看每一页有没有"张"字。在MySQL里,这就是全表扫描。

用EXPLAIN看最直观:

EXPLAIN SELECT * FROM users WHERE name LIKE '张%'; -- type: range,说明使用了索引范围扫描 EXPLAIN SELECT * FROM users WHERE name LIKE '%张%'; -- type: ALL,说明是全表扫描

如果表里有几百万条数据,%张%这种查询的耗时可能会从前缀匹配的几十毫秒暴涨到几秒甚至几十秒。

3.2 通配符查询的量级评估与调优思路

什么时候该关注通配符性能?我个人的经验是:当单表数据量超过100万行,且模糊查询是核心业务路径时,必须认真对待。

有几个调优方向可以组合使用:

第一,尽可能使用前缀匹配。如果业务允许,把'%关键词%'改成'关键词%'。比如搜索用户时,优先按用户名开头匹配,而不是包含匹配。

第二,避免多个前导通配符的组合。LIKE '%张%三%'这种写法比LIKE '%张三%'慢得多,因为需要匹配的位置更多。

第三,用覆盖索引减少回表。如果查询只需要返回name和id,可以在name上建索引,并且只查这两个字段。这样即使LIKE '张%'走了索引,也无需回表读取整行数据,性能会好很多。

第四,考虑全文索引(FULLTEXT)。真正需要做"包含匹配"的场景,比如搜索文章内容、商品描述,MySQL内置的全文索引比LIKE '%关键词%'快得多。全文索引是倒排索引的实现,专门为"文本里包含某个词"这种查询设计的。MySQL 8.0支持中文全文索引,用ngram分词器:

ALTER TABLE articles ADD FULLTEXT INDEX ft_title_content (title, content) WITH PARSER ngram; SELECT * FROM articles WHERE MATCH(title, content) AGAINST('数据库' IN NATURAL LANGUAGE MODE);

不过全文索引也有自己的限制,比如对短词、停用词的处理,以及对分词器的依赖。它适合的是"全文搜索"场景,不适合"用户名模糊匹配"这种短字段场景。

3.3 当通配符不够用:REGEXP正则的补充

通配符的匹配能力其实很有限,它只能表达"有任意字符""有固定一个字符",表达不了更多复杂的规则。如果你要匹配 "手机号是以138或139开头" 这种多选一的条件,通配符写起来很别扭:

SELECT * FROM users WHERE phone LIKE '138%' OR phone LIKE '139%';

用正则表达式就简洁得多:

SELECT * FROM users WHERE phone REGEXP '^13[89]';

MySQL的REGEXP支持完整的正则语法。字符类[89]、量词{m,n}、分组()都能用。相比LIKE,REGEXP表达力强了不止一个量级。

但要注意两个问题。第一,REGEXP通常是无法使用索引的,性能比LIKE '前缀%'差,适合数据量不大或者低频查询的场景。第二,正则表达式的写法一定要提前测试,我见过不少人把^\d{11}$写错成\d{11},结果匹配出一堆乱七八糟的数据。

如果需要做一个"既能表达复杂规则,又能兼顾性能"的方案,实际项目中更常见的做法是:用LIKE '前缀%'缩小范围(走索引),再用REGEXP做精确过滤。两个条件用AND连接,MySQL优化器通常会先执行能走索引的条件。

4. 通配符实战场景:从简单到复杂的完整案例

4.1 用户搜索场景

以一个典型的后台用户管理页面为例,搜索框需要支持按姓名模糊搜索。Java或PHP后端代码里通常这样拼接SQL:

SELECT id, name, phone, email, created_at FROM users WHERE name LIKE CONCAT('%', ?, '%') ORDER BY created_at DESC LIMIT 20;

参数化查询配合?占位符,可以避免SQL注入。这里的LIKE CONCAT('%', ?, '%')是一种规范的写法,比提前在业务代码里拼好'%' + keyword + '%'更安全。

但我在实际优化中会建议改成这样:

SELECT id, name, phone, email, created_at FROM users WHERE name LIKE CONCAT(?, '%') ORDER BY created_at DESC LIMIT 20;

前提是产品可以接受"只按姓名开头搜索"。如果产品经理坚持要做"包含"搜索,也可以加一个折中方案:优先返回"前缀匹配"的结果,再返回"包含匹配"的结果,用ORDER BY控制顺序:

SELECT id, name, phone, email FROM users WHERE name LIKE CONCAT('%', ?, '%') ORDER BY CASE WHEN name LIKE CONCAT(?, '%') THEN 0 ELSE 1 END, created_at DESC LIMIT 20;

这个写法的思路是:把"以关键词开头"的记录排在前面,其余包含关键词的排后面。兼顾了搜索体验和排序逻辑。

4.2 数据清洗与批量匹配场景

通配符在数据清洗中也有非常实际的应用。比如有一张订单表,order_no字段格式混乱,有的带前缀"OD-"有的不带。你想找出所有"看起来像订单号"的记录:

SELECT * FROM orders WHERE order_no LIKE 'OD-%' OR order_no LIKE 'OD\_%' ESCAPE '\' OR order_no REGEXP '^OD-?[0-9]{6,}$';

这个例子混合了LIKE、ESCAPE和REGEXP三种语法,实际场景中很常见。但要注意一个问题:多个OR条件连接时,MySQL可能无法有效利用索引,即使每个条件本身都能走索引。这时可以用UNION改写:

SELECT * FROM orders WHERE order_no LIKE 'OD-%' UNION SELECT * FROM orders WHERE order_no REGEXP '^OD-?[0-9]{6,}$';

UNION会去重,性能一般比多个OR稳定。但如果数据量不大,直接用OR写法更简洁,不必过度优化。

4.3 通配符与排序、分页的协同

通配符查询和ORDER BY、LIMIT组合时,有一个性能陷阱值得注意。当LIKE '%关键词%'匹配出大量数据,又要按某个字段排序并分页时,MySQL必须先对所有匹配结果排序,再取需要的页。这个排序过程如果发生在全表扫描之后,性能会很差。

常见的优化方案是把"分页查询"改成"基于游标的分页"。也就是用上次查询返回的最后一条记录的id作为下一次查询的起点:

-- 第一页 SELECT id, name FROM users WHERE name LIKE '%张%' ORDER BY id LIMIT 20; -- 第二页,传入上一页最后一条记录的id SELECT id, name FROM users WHERE name LIKE '%张%' AND id > 上一页最大id ORDER BY id LIMIT 20;

这种做法的好处是id > ?条件可以使用主键索引,配合ORDER BY id LIMIT 20非常高效。缺点是它要求排序字段是唯一的、递增的,如果用户需要按其他字段排序就不好办了。

如果必须按created_at这类非唯一字段排序,同时保持分页稳定,就要在ORDER BY里加一个唯一字段作为第二排序键:ORDER BY created_at DESC, id DESC。否则会出现翻页时数据重复或丢失的问题。

5. 通配符常见问题与排查技巧实录

5.1 通配符匹配中文查不到数据

很多人遇到过LIKE '%张%'查不到"张三丰"的情况。排查思路依次是:

检查排序规则。如果字段是utf8mb4_bin,中文匹配是区分大小写?其实中文不存在大小写,但_bin排序规则要求完全一致的字节序列。"张"和"張"(繁体)在这种排序规则下是不同的。

检查字段是否有隐藏字符。从外部导入的数据,可能带有不可见的字符,比如UTF-8 BOM头、零宽空格。用下面的SQL把字段转成十六进制看看:

SELECT id, HEX(name) FROM users WHERE id = 1;

"张三丰"的HEX应该是E5BCA0E4B889E4B8B0这种格式。如果中间冒出E2808B(零宽空格)之类的字节序列,就说明数据本身不干净,用REPLACE清洗掉即可。

检查字符集配置。连接字符串里的character_set_connection和表的字符集不一致时,LIKE比较可能产生乱码。在连接数据库后执行:

SET NAMES utf8mb4;

这是排查中文匹配问题最常用的第一步,能解决八成以上的显示乱码和匹配失败问题。

5.2 通配符与NULL、空字符串的边界情况

很多人在写条件时忽略了NULL和空字符串的区别。LIKE '%'能匹配所有非NULL的字符串,但匹配不了NULL。也就是说:

-- 这条SQL不会返回 name 为 NULL 的记录 SELECT * FROM users WHERE name LIKE '%'; -- 但会返回 name 为 '' 的记录

这个行为让不少人困惑。如果你的业务逻辑是"筛选出所有填了姓名的用户",应该写:

SELECT * FROM users WHERE name IS NOT NULL AND name != '';

如果只是想统计"有值"的姓名,更高效的写法是直接WHERE name IS NOT NULL,不要用LIKE '%',因为在某些版本和配置下,LIKE '%'可能会带来额外的性能开销。

5.3 通配符出现在数据本身里

我之前负责过一个问卷系统,用户在填空题里可以输入任意内容,包括"填写率100%"这种文本。后来做统计时,用LIKE '填写率100%'查对应记录,发现查出了所有问卷,因为%被当成了通配符。

这种问题的核心是:用户输入的数据里的特殊字符,在拼接到SQL之前就要做好转义。在业务代码里,需要写一个转义函数,把用户输入中的%、_、\统一替换为带转义符的形式。比如在PHP里:

$keyword = str_replace(['\\', '%', '_'], ['\\\\', '\\%', '\\_'], $keyword);

然后在SQL里用LIKE CONCAT('%', ?, '%')匹配时,才能保证用户输入的%被当作字面量处理,而不是通配符。

这里有一个容易忽略的细节:转义符\在PHP双引号和单引号字符串里,以及在MySQL解析层里,都各自有一层转义逻辑。顺序搞错了,转义就失效。我建议所有人在代码里写这种转义逻辑时,先输出一下转换后的字符串看看,确认没问题再拼SQL。

6. 通配符使用速查表

把我这些年用到过的通配符场景整理成一个速查表,方便日常参考:

场景写法说明
任意前缀LIKE '关键词%'走索引,性能好
任意后缀LIKE '%关键词'全表扫描
任意包含LIKE '%关键词%'全表扫描,性能最差
固定单个字符LIKE '张_'匹配"张"加任意一个字
多个任意字符LIKE '张%丰'匹配"张"开头"丰"结尾的任意长度
字面量百分号LIKE '%50\%%'用反斜杠转义
字面量下划线LIKE '%\_%'用反斜杠转义
自定义转义符LIKE '%50!%%' ESCAPE '!'显式指定转义符
多选一前缀REGEXP '^13[89]'正则表达式表达力更强
全文包含搜索MATCH(...) AGAINST(...)全文索引,适合大数据量文本

这张表在面试和日常开发里都挺实用。很多面试官喜欢问"通配符优化"的问题,本质上就是想考察你是否理解索引失效的原理。把这几点说清楚,基本就能过关。

我个人在实际操作中的体会是:通配符是个看似简单、实则细节极多的功能。很多人用了几年MySQL,遇到%和_还是分不清优先级,遇到特殊字符转义还是一脸懵。这并不丢人,因为这个东西确实只有在真实业务里踩过坑,才会真正重视起来。建议你把自己手头的表拿出来,按照上面的案例挨个跑一遍,尤其是EXPLAIN看索引的部分。亲眼看到type: ALL和type: range的区别,比看十篇文章都管用。

需要专业的网站建设服务?

联系我们获取免费的网站建设咨询和方案报价,让我们帮助您实现业务目标

立即咨询