一个模糊查询让我的接口超时了

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