前两天给博客加了个文章搜索功能,用户输入关键词,后端查 MySQL。
写完上线,一切正常。
五分钟后用户群炸了——搜索接口超时,等了 4 秒才返回。
第一反应:数据不多啊
博客总共几百篇文章,title字段加了索引。按理说应该秒出。
打开 SQL 日志一看:
SELECT*FROMarticlesWHEREtitleLIKE'%微服务%'ANDdeleted=0LIKE '%xxx%'——左模糊。一瞬间反应过来了。
MySQL 的 B+Tree 索引是按字符串从左到右排序的。左边是通配符,引擎不知道从哪开始找,索引直接报废。
EXPLAIN验证:
+----+------+---------------+------+---------+------+------+------------+ | id | type | possible_keys | key | key_len | ref | rows | Extra | +----+------+---------------+------+---------+------+------+------------+ | 1 | ALL | idx_title | NULL | NULL | NULL | 387 | Using where| +----+------+---------------+------+---------+------+------+------------+type=ALL,全表扫描。387 行不多,但每行都要走一遍LIKE匹配,不超时才怪。
LIKE 模糊查询的索引规则
只有一种写法能走索引:
-- ✅ 走索引WHEREtitleLIKE'微服务%'-- ❌ 不走索引WHEREtitleLIKE'%微服务'WHEREtitleLIKE'%微服务%'前缀匹配没问题,因为 B+Tree 能定位到「微」字开头的所有记录。但一旦左边有%,引擎就不知道从索引树的哪个节点开始找了。
不只是 LIKE:五种最常见的索引失效场景
排查过程不是孤立事件。顺手整理了五个日常开发里最容易踩的坑。
1. 对索引列做函数操作
-- ❌ 不走索引WHEREDATE(create_time)='2026-07-13'-- ✅ 走索引WHEREcreate_time>='2026-07-13 00:00:00'ANDcreate_time<'2026-07-14 00:00:00'索引存的是原始值,套了DATE()函数它就认不出来了。范围查询是正解。
2. 隐式类型转换
-- phone 是 VARCHAR 类型,加了索引-- ❌ 不走索引WHEREphone=13558035960-- ✅ 走索引WHEREphone='13558035960'MySQL 做类型转换时会把字符串转数字去比较,等于给索引列套了一层隐式函数。EXPLAIN里会看到Using index condition但实际type是index不是ref。
3. OR 条件
-- ❌ 部分不走索引WHEREtitle='微服务'ORcontentLIKE'%部署%'-- ✅ 用 UNION 改写SELECT*FROMarticlesWHEREtitle='微服务'UNIONSELECT*FROMarticlesWHEREcontentLIKE'%部署%'OR 两边只要有一边索引失效,整个查询走全表。拆成 UNION,各自走各自的索引。
4. NOT、!=、<> 负向查询
-- ❌ 不走索引WHEREstatus!=1WHEREstatusNOTIN(1,2,3)-- ✅ 走索引WHEREstatus=0WHEREstatusIN(0,4,5)优化器认为负向查询会扫大部分数据,不如全表快。能正向写就正向写。
5. 联合索引不满足最左前缀
-- 联合索引:(a, b, c)-- ✅ 走索引WHEREa=1WHEREa=1ANDb=2WHEREa=1ANDb=2ANDc=3-- ❌ 不走索引(跳过了 a)WHEREb=2WHEREb=2ANDc=3-- ⚠️ 只走 a 的索引,b 不走WHEREa=1ANDc=3联合索引像字典——先按 a 排序,a 相同再按 b 排序。跳过 a 直接查 b,索引树用不上。
回到我的搜索功能:三种解法
方案一:全文索引(推荐)
MySQL 5.7+ 内置中文分词(ngram),建全文索引后MATCH ... AGAINST走全文检索,LIKE '%xxx%'的问题绕过去了。
ALTERTABLEarticlesADDFULLTEXTINDEXft_title(title)WITHPARSER ngram;SELECT*FROMarticlesWHEREMATCH(title)AGAINST('微服务'INBOOLEANMODE)ANDdeleted=0;几百篇文章,查询从 4 秒降到 0.05 秒。
方案二:Elasticsearch
文章量大了全文索引扛不住再上 ES。现阶段没必要,一台 2C4G 的 ECS 跑不动 ES。
方案三:前端防抖 + 缓存
搜索框输入防抖 300ms,热门关键词缓存到 Redis。没打到索引层的问题,但能降负载。这次没用上,先记着。
我选了方案一——改动最小,效果立竿见影。
五个索引失效场景,本质上是一个问题:索引是按规则排好的,你打破了规则,它就罢工。
写完代码跑一遍EXPLAIN,花十秒钟,省掉半夜爬起来改 SQL 的噩梦。
关于作者:无羡,独立开发者,全栈工程师,专注 AI 应用与微服务架构。
📌 分类:独立开发实战
🔗 个人博客 — 更多技术文章
✨ 开发者福利整理 — 云服务资源汇总
📂 软件工坊 — 我的技术分享
🌐 个人门户 — 独立开发作品全集