☰
MySQL通配符深度解析:LIKE匹配规则、转义与索引性能优化
2026/10/6 13:30:45 网站建设 项目流程

做MySQL查询时,通配符是逃不掉的话题。你写SELECT语句,从用户表里捞数据,十有八九会用LIKE加一个%或_去做模糊匹配。但很多人只记住了“%代表任意字符”,真遇到数据里有百分号、下划线,或者查询慢到让人抓狂时,就不知道问题出在哪了。这篇文章会把MySQL中通配符的匹配规则、常用场景、性能坑和替代方案一次讲透。

适合刚写完第一版CRUD、准备把查询写得更优雅的同学,也适合被慢查询折磨过、想系统梳理一遍的开发。不管你是本机装MySQL 8.0,还是用Docker拉一个MySQL实例,下面这些规则都适用。我会把匹配原理、实操案例、索引影响和常见问题放在一起讲,这样你在写SQL时不光知道怎么用,还能知道为什么能这么用。

1. 通配符搜索的整体设计与使用场景

1.1 为什么需要通配符:精确匹配解决不了“大概符合”的需求

先说个最直观的问题:WHERE price = 100这样的等值查询确实快,但业务里大量需求是“用户记不全名字”“商品分类不固定”之类的模糊条件。搜索“华为”时,后台希望把“华为Mate”“华为充电器”“二手华为P40”都捞出来,条件不可能写成name = '华为',这时候就需要LIKE和通配符。

通配符的本质是给查询条件加入“部分匹配”的能力,让数据库在字符串里按规则找位置,而不是要求两个值完全一样。LIKE是SQL标准里的操作符,MySQL对它的实现也很成熟,性能上限取决于你的匹配模式和索引使用方式。要注意的是,LIKE和正则表达式(REGEXP)经常被人混在一起说,其实是两个层次的工具。LIKE只有两个通配符:%和_,外加一个转义机制,规则简单、可读性好;正则表达式能表达字符集、重复次数、锚点这些复杂规则,但代价是更难看懂、更容易踩边界。

对大部分业务查询来说,LIKE已经够了,正则更适合做数据清洗、复杂格式校验,后面会单独讲怎么选。我还想强调一点:通配符不是“慢查询”的同义词,它只是提供了灵活匹配能力。用得好,一个简单的LIKE 'abc%'同样能走索引;用得不好,哪怕表只有十万行,也可能变成一次灾难性的全表扫描。

1.2 典型应用场景:从搜索框到数据清洗

我在实际项目里,通配符用得最多的场景大概是这几类:

  • 前台搜索框的关键词匹配,比如商品名称、文章标题、联系人姓名。
  • 根据已知片段筛选账号,比如知道邮箱前缀或者域名后缀,想查“所有@qq.com用户”。
  • 日志和订单编号的模糊排查,比如查“所有pint_2024开头的记录”。
  • 数据迁移和清洗时的批量识别,比如找出临时表、找出格式不规范的手机号。
  • 关联查询中做弱匹配,比如两个系统里名称略有差异的客户做合并。

这些场景有一个共同点:匹配目标不是完整值,而是“一部分”。通配符正好解决这类问题。但如果你把通配符当作万能钥匙,不加思考地在每个查询里都用LIKE '%关键词%',很快就会遇到性能问题,这个放后面讲。先老老实实把匹配规则搞明白,再谈优化。

除此之外,还有一类场景容易被忽略:运维和数据分析人员经常用通配符批量处理表名或字段名,比如查information_schema里所有tmp_%前缀的临时表。这种场景下通配符不是用在一行行数据上,而是用在元数据上,同样依赖LIKE的匹配能力。理解通配符的核心逻辑,对你以后写各种脚本都很有帮助。

2. 核心通配符详解与匹配规则

2.1 百分号%的用法与边界

%是通配符里的“长匹配符”,它能匹配任意数量的字符,包括0个字符。比如:

SELECT product_id, name, price FROM products WHERE name LIKE '%手机%';

这条语句会匹配所有name里包含“手机”两个字的商品,前后有没有别的字都不重要。同理,LIKE '手机%'匹配以“手机”开头的记录,LIKE '%手机'匹配以“手机”结尾的记录,LIKE '%手机%'就是包含匹配。这里有个容易忽略的点:%也能匹配0个字符,所以LIKE '手机%'也可以匹配name恰好等于“手机”的记录,不要认为%后面必须有字才算匹配。

按常规思考,LIKE '%%'总该匹配所有行了吧?它确实能匹配所有name不为NULL的行,但代价是全表扫描,没有任何索引优化空间。我见过同事拿LIKE '%%'去“查重”,结果数据一多直接把接口拖垮。正确做法是直接判断空字符串或者IS NOT NULL。

还有一点,%和_都是针对非NULL的有效值做匹配的,如果字段值是NULL,那么任何LIKE条件都不会命中,因为NULL代表“未知”,未知和任何模式比较的结果都是“未知”。很多人写NOT LIKE时最容易在这里翻车,后面我会专门展开。

2.2 下划线_的精确匹配

_是“单字符占位符”,它只匹配任意一个字符,不多不少。这个通配符很适合做定长格式的筛选。例如:

SELECT user_id, username FROM users WHERE username LIKE 'A____';

你看到的是“A”后面4个下划线,含义就是用户名以A开头、总长度刚好5个字符。要表达“至少5个字符”,可以写'A_____%':A开头,至少5位,后面多少字符不限制。要表达“第二个字符是某个数字”这种需求,_同样能占位,但数字与否得靠正则表达式来约束,LIKE本身没有字符类概念。

很多人会把_和%混用,出问题最多的地方是忘记_只占一位。比如WHERE name LIKE 'ab_'和'ab%',前者只匹配三位、前两位是ab的记录,后者匹配所有以ab开头的记录。写条件前最好在注释里标明意图,不然同事接手时容易看晕。

-- 查询所有以ab开头、总共3位的名称 SELECT * FROM products WHERE name LIKE 'ab_'; -- 查询所有以ab开头的名称,长度不限制 SELECT * FROM products WHERE name LIKE 'ab%';

这两个例子放一起,差异就很明显了。日常开发里我更喜欢让团队统一用%做“开放匹配”,用_只处理固定位数的场景,比如证件号、手机号规律筛选、日志流水号段提取。这样规则越简单,出错的概率越低。

2.3 ESCAPE转义:让特殊字符回归字面意义

%和_作为通配符有特殊含义,但数据里如果真的要包含这两个字符呢?比如商品名称是“折扣50%起”,想按字面搜“50%”,直接写LIKE '%50%%'就出事了,因为数据库会把你写的多个%都当成通配符。这时候需要把%转义成普通字符。

MySQL默认支持用反斜杠转义,但也允许你自定义转义字符,我习惯用ESCAPE子句,因为可读性更好:

SELECT product_id, name FROM products WHERE name LIKE '%50#%%' ESCAPE '#';

这段SQL的意思很明确:第一个%是任意前缀,中间的#%表示一个字面的百分号,最后的%是任意后缀。这个写法能查到“折扣50%起”,也能查到“手机50%特惠”。

下划线也同理,如果你要匹配的是“A_B”这种带下划线的字符串,直接写LIKE 'A_B'会把下划线当成占位符,写成:

SELECT * FROM products WHERE name LIKE 'A#_B' ESCAPE '#';

这里还有一个很容易栽的坑:MySQL字符串本身在默认配置下把反斜杠当转义字符。如果你用默认转义,想在LIKE里匹配一个反斜杠,就得写双反斜杠,否则SQL里的\会被吃掉。比如LIKE 'C:\Users\%'很可能实际匹配的是“C:Users%”,因为\U不是一个合法的MySQL转义序列,行为会变得很怪。

我建议别依赖默认反斜杠,统一用ESCAPE '#!'或类似符号来转义,团队维护时少踩很多雷。而且要注意转义字符本身不能出现在你预判的数据里,否则会造成二次转义问题。设计通配符查询时,把转义规则写清楚,比事后追查结果异常要省心得多。

3. 实操案例:从基础查询到复杂筛选

3.1 商品表里的模糊搜索与排序

用一个实际例子把上面规则串起来。假设有一张商品表:

CREATE TABLE products ( id INT PRIMARY KEY AUTO_INCREMENT, name VARCHAR(100) NOT NULL, category VARCHAR(50), price DECIMAL(10, 2), created_at DATETIME, KEY idx_category_name (category, name) );

业务要求:在“手机”分类下搜索名称里带“Pro”的商品,并按价格从高到低排序,只取10条。

SELECT id, name, price FROM products WHERE category = '手机' AND name LIKE '%Pro%' ORDER BY price DESC LIMIT 10;

这个查询有两个值得注意的地方。第一,LIKE '%Pro%'前导通配符会导致name相关的索引基本发挥不了作用,但因为有category = '手机'这个等值条件,复合索引idx_category_name还是能先定位到“手机”分类,再把该分类下的行交给LIKE过滤。如果category列的区分度高,这个查询依然会比没有索引快得多。

第二,ORDER BY price和LIMIT 10并不是陷阱,结果集经过LIKE过滤后再排序,数据量不大时压力很小;但如果你把LIKE条件换成不带分类条件,对整个商品表做扫描再加文件排序,就会很吃力。之前提到的热搜词里有“mysql排序”,这里正好是一个典型场景:模糊匹配和排序是可以共存的,关键是让过滤更早地缩小结果集,而不是先把全表排好序再过滤。

3.2 用户表里的域名与定长账号匹配

用户表里经常要做这种查询:找出所有邮箱属于某个域名的人。比如所有腾讯邮箱用户:

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

有人会问:既然后缀固定,为什么不写RIGHT(email, 7) = '@qq.com'?理由很简单,函数包裹字段会让索引失效,而且每次都要计算RIGHT。LIKE '%@qq.com'也是前导通配符,同样会扫描,但写法上意图更清楚。数据量小时两者差距不大;数据量大时,我建议改成冗余字段或全文索引方案。

如果需求是“用户名第3个字符是a”,用_就能解决:

SELECT id, username FROM users WHERE username LIKE '__a%';

这条SQL查的是:前两个字符任意,第三个字符必须是a,后面内容不限。它比SUBSTRING(username, 3, 1) = 'a'要清晰,性能在中小表也更好。但有个细节要注意:如果字段的字符集是utf8mb4,并且用户名里存在中文或表情符号这种多字节字符,_按字符匹配而不是按字节匹配,绝大多数情况下按字符理解是符合直觉的,不用自己吓自己。

这种定长匹配在数据清洗场景中尤其有用。比如有一批手机号不规范的记录,想找出“前三位130,后面8位数字”的数据,用LIKE '130________'加正则校验,能快速筛出候选集。它不一定能精准校验每一位是不是数字,但可以先把格式对不上的明显脏数据过滤掉。

3.3 聚合、分页与动态SQL中的通配符

通配符不能只停留在简单的SELECT里,业务上经常要和COUNT、GROUP BY一起用。例如统计“名称带Pro”的商品数量:

SELECT COUNT(*) AS total FROM products WHERE name LIKE '%Pro%';

也可以按分类统计哪些分类下面Pro产品最多:

SELECT category, COUNT(*) AS cnt FROM products WHERE name LIKE '%Pro%' GROUP BY category ORDER BY cnt DESC;

这种组合本身没有坑,但有两点要注意。一是COUNT(*)和LIKE前导通配符的组合会让数据库做全表扫描,扫完才聚合,表特别大时要先想清楚是否值得。我一般会先用EXPLAIN看一眼扫描行数,再决定要不要加缓存。

二是在存储过程或ORM里动态拼接SQL时,千万别直接把用户输入拼进LIKE条件。恶意输入里如果带%和_,不光语义会错,还可能变成一次意外的全表扫描;更严重的是触发了SQL注入。正确做法是使用预编译参数,MySQL PREPARE或MyBatis的#{}都能避免字符串层面的混乱。

PREPARE stmt FROM 'SELECT * FROM products WHERE name LIKE CONCAT("%", ?, "%")'; SET @keyword = 'Pro'; EXECUTE stmt USING @keyword;

这样用户输入的%就只是普通查询内容的一部分,不会被当成通配符,整个查询逻辑也干净很多。

4. 常见坑与性能问题排查

4.1 前导通配符与索引失效

这是通配符查询里最有名的一句话:LIKE 'abc%'能用索引,LIKE '%abc'和LIKE '%abc%'基本不能用索引。原因很直白,B+树的索引是按完整前缀排序的,查询条件一旦允许匹配字符串的任意位置,数据库就没法用有序结构做快速定位,只能回表扫描。

具体到执行计划,能看到type为ALL或者index,而不是range。要快速验证,直接在查询前加EXPLAIN:

EXPLAIN SELECT * FROM products WHERE name LIKE '%Pro%';

如果扫描行数很大,你需要认真考虑优化。常用的手段有几个:

  • 把业务拆细,尽量加等值条件缩小范围,比如先按分类、状态过滤。
  • 对字段建立前缀索引,但前缀索引只能帮到左侧匹配,对%关键词%无效。
  • 用覆盖索引减少回表,但同样是治标不治本。
  • 将搜索能力外置,比如同步到搜索引擎或全文索引。

这里还要说一个容易混淆的点:LIKE 'abc%'虽然能走索引,但如果你的排序规则是utf8mb4_bin,大小写严格区分,那么LIKE 'ABC%'就匹配不到小写开头的记录;如果用的是_ci结尾的排序规则,则不区分大小写。很多团队在排查“明明建了索引为什么没走”时,会忽略collation对LIKE匹配结果和索引选择的影响。

4.2 大小写、NULL与转义的细节坑

LIKE是否区分大小写,不是LIKE自己的问题,而是字段排序规则(collation)决定的。utf8mb4_general_ci这种_ci结尾的排序规则不区分大小写,LIKE 'abc%'能匹配Abc;utf8mb4_bin这种二进制排序规则会区分。

如果你希望强制统一行为,可以显式给字段加COLLATE,但要注意这会让索引失效的案例变得更复杂。最简单的建议是:建表时就把大小写规则定好,别在查询时靠函数强行转换。比如WHERE LOWER(name) LIKE '%abc%',字段套了函数,索引直接就放弃了。

NULL也是一个经典陷阱。WHERE name LIKE '%abc%'不会匹配NULL,因为NULL代表未知,任何比较结果都是未知。但更坑的是WHERE name NOT LIKE '%abc%'也会排除NULL,很多人在做排除型查询时漏掉了NULL数据。正确的写法是:

WHERE (name NOT LIKE '%abc%' OR name IS NULL)

这个细节在数据清洗时特别常见。比如想筛选出“名称里不包含测试字样”的商品,如果直接NOT LIKE '%测试%',那些name为NULL的脏数据会被一并丢掉,影响统计结果。显式处理NULL才能避免误伤。

转义问题在前面讲过,但实际操作中还有一个高频错误:很多人以为只要把用户输入里的%替换成\%就不会出错,可是MySQL字符串默认把反斜杠也当转义符,替换后的\%在字符串解析层就已经消掉了反斜杠,最后到了LIKE层还是通配符。这就是为什么我更推荐自定义ESCAPE,而不是手工处理反斜杠。

4.3 慢查询排查与替代方案选型

排查通配符慢查询,第一步永远是EXPLAIN,看走没走索引、扫了多少行。第二步是看前缀占比,前导通配符的查询在大表上基本不可能快,慢不是偶然,而是匹配模式决定的。第三步才是考虑替代方案。

LIKE、REGEXP和全文索引(FULLTEXT)的选择,我一般这样判断:

方案适合场景性能可读性风险
LIKE简单的左右或包含匹配左匹配可用索引,前导通配符全扫高转义麻烦
REGEXP复杂模式、格式校验通常全扫,难以优化低正则边界难把握
FULLTEXT大量文本的中文或英文搜索使用倒排索引,速度快中分词、同步要处理

如果你在建一个站内搜索,千万别用LIKE '%关键词%'硬扛,应该考虑FULLTEXT。MySQL InnoDB的全文索引在5.7之后已经比较稳定,中文可以用ngram全文解析器,配合BOOLEAN MODE也能做包含匹配:

SELECT id, title FROM articles WHERE MATCH(title, body) AGAINST ('+数据库 +通配符' IN BOOLEAN MODE);

性能上,全文索引比LIKE '%关键词%'好几个数量级,但代价是索引数据要维护、分词规则要调,不适合随手加。反过来说,如果只是后台管理列表里的临时筛选,数据量一两百万以内,LIKE前导通配符加合理缓存也能接受,不一定非要上搜索引擎。做技术选型,最重要的是知道你面对的数据量级和实时性要求。

还有一个场景经常被忽略:如果你用的是MySQL 8.0,REGEXP的底层实现已经换成了ICU库,多字节字符处理比老版本稳定很多,但业务大表上高频使用正则仍然不推荐。正则更适合做一次性数据清洗、复杂格式校验,不适合塞进核心查询链路。能用LIKE解决的需求,就不要贪图正则的表达能力。

4.4 我踩过几次坑之后的个人建议

最后说点个人经验。做MySQL通配符查询,我最看重的是“先想清楚匹配模式,再去写SQL”。很多人一上来就写LIKE '%关键词%',结果条件根本满足不了业务,比如“以某个字母开头”的需求被写成了包含匹配,既慢又错。我自己的习惯是:先确认业务要的是前缀、后缀还是包含,再对照数据实际分布决定索引和缓存方案。

另一个建议是尽量把通配符相关的转义逻辑封装成函数或工具方法,尤其是项目里ESCAPE字符、大小写规则、NULL处理这种容易不一致的地方。如果团队里有人踩过“数据里带%导致查询结果不对”的坑,下次改代码时就会格外小心。MySQL的通配符规则并不复杂,真正难的是在各种业务场景里保持一致的判断,把基础规则变成团队的肌肉记忆。

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

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

立即咨询