ARTICLE DETAIL

资讯详情

深耕网站建设与运营推广的一线实战洞察。

MySQL通配符深度解析:LIKE匹配规则、转义与索引性能优化

MySQL通配符深度解析:LIKE匹配规则、转义与索引性能优化 做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。要快速验证直接在查询前加EXPLAINEXPLAIN 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%能匹配Abcutf8mb4_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.0REGEXP的底层实现已经换成了ICU库多字节字符处理比老版本稳定很多但业务大表上高频使用正则仍然不推荐。正则更适合做一次性数据清洗、复杂格式校验不适合塞进核心查询链路。能用LIKE解决的需求就不要贪图正则的表达能力。4.4 我踩过几次坑之后的个人建议最后说点个人经验。做MySQL通配符查询我最看重的是“先想清楚匹配模式再去写SQL”。很多人一上来就写LIKE %关键词%结果条件根本满足不了业务比如“以某个字母开头”的需求被写成了包含匹配既慢又错。我自己的习惯是先确认业务要的是前缀、后缀还是包含再对照数据实际分布决定索引和缓存方案。另一个建议是尽量把通配符相关的转义逻辑封装成函数或工具方法尤其是项目里ESCAPE字符、大小写规则、NULL处理这种容易不一致的地方。如果团队里有人踩过“数据里带%导致查询结果不对”的坑下次改代码时就会格外小心。MySQL的通配符规则并不复杂真正难的是在各种业务场景里保持一致的判断把基础规则变成团队的肌肉记忆。
返回列表