为什么加了索引还慢?MySQL 索引失效 12 个排查点

张开发
2026/4/22 16:39:00 15 分钟阅读

分享文章

为什么加了索引还慢?MySQL 索引失效 12 个排查点
这篇给你一个能直接拿去线上排查的版本不讲玄学只讲怎么定位。先看结论SQL 慢、明明建了索引通常不是“索引没建”而是建了但没用上用上了但扫描行太多用上了但回表成本太高排查顺序就三步EXPLAIN、看执行条件、改 SQL/索引设计。一、先用 EXPLAIN 别猜先跑EXPLAINSELECT*FROMordersWHEREuser_id10086ANDstatus1;重点看这几个字段字段怎么看type至少要到ref最好range/constkey实际命中的索引rows预估扫描行数越小越好Extra有没有Using filesort、Using temporary如果key NULL先别急着骂 MySQL通常是写法把索引“写废了”。二、最常见的 12 个索引失效点1) 在索引列上做函数-- 失效写法SELECT*FROMuserWHEREDATE(create_time)2026-03-29;DATE(create_time)让 BTree 没法按原值匹配。改成范围查询SELECT*FROMuserWHEREcreate_time2026-03-29 00:00:00ANDcreate_time2026-03-30 00:00:00;2) 隐式类型转换-- phone 是 varchar右边给了数字SELECT*FROMuserWHEREphone13800138000;字段类型和常量类型不一致会导致转换索引可能失效。统一类型SELECT*FROMuserWHEREphone13800138000;3) 前导模糊匹配SELECT*FROMproductWHEREnameLIKE%手机;以%开头无法走普通索引。可选方案倒排索引ES、前缀搜索重构、全文索引。4)OR两边索引条件不对称SELECT*FROMordersWHEREuser_id1ORremarkurgent;一边可走索引一边不走优化器可能直接全表扫。可拆成UNION ALL两段分别优化。5) 复合索引没按最左前缀用有索引(user_id, status, create_time)你却写SELECT*FROMordersWHEREstatus1;没带最左列user_id这个复合索引通常用不上。6) 范围条件后面的列无法继续高效利用-- 索引(user_id, create_time, status)SELECT*FROMordersWHEREuser_id1ANDcreate_time2026-03-01ANDstatus1;命中到范围列后后续列的过滤收益会明显下降。常见做法调整复合索引顺序把等值高选择度列尽量放前面。7)!//NOT IN选择性差SELECT*FROMuserWHEREstatus1;这类条件经常要扫大范围优化器可能放弃索引。8) 回表太重优化器宁可全表扫SELECT*FROMordersWHEREuser_id10086;如果命中行太多SELECT *触发大量回表可能不划算。尽量改成覆盖索引需要的列SELECTid,user_id,statusFROMordersWHEREuser_id10086;9) 排序和索引方向不匹配SELECT*FROMordersWHEREuser_id1ORDERBYcreate_timeDESC;索引不匹配时会出现Using filesort。要么补合适索引要么减少排序数据集。10) 分组导致临时表GROUP BY字段若没有合适索引常见Using temporary。先过滤再分组或增加对应索引。11) 统计信息过旧数据分布变化后优化器可能选错执行计划。可执行ANALYZETABLEorders;12) 小表全扫本来就更快这个不是失效是优化器做了正确决策。几千行的小表全表扫可能比走索引回表还快。三、一个实战排查流程我平时这么走抓慢 SQL慢日志 / APM跑EXPLAIN看key/type/rows/Extra检查是否存在上面 12 类问题优先改 SQL 写法函数、类型、范围、SELECT *再改索引设计复合索引顺序、覆盖索引回归压测对比耗时和扫描行先改 SQL 往往比“无脑加索引”收益更大。四、给你一套可复用的优化模板慢 SQLSELECT*FROMordersWHEREDATE(create_time)2026-03-29ANDstatus1ORDERBYcreate_timeDESCLIMIT20;优化后-- 1) 改写时间条件避免函数-- 2) 减少返回列尽量覆盖索引SELECTid,user_id,status,create_timeFROMordersWHEREcreate_time2026-03-29 00:00:00ANDcreate_time2026-03-30 00:00:00ANDstatus1ORDERBYcreate_timeDESCLIMIT20;配套索引思路-- 按你的查询条件设计复合索引示例CREATEINDEXidx_orders_status_ctimeONorders(status,create_time);五、最后的建议排查索引问题时别问“有没有索引”要问三件事这个索引有没有被用到用到后扫描行是不是还很大回表和排序是不是把收益吃掉了你把这三件事盯住MySQL 慢查询会少掉一大半。

更多文章