为什么加了索引查询还是慢:B+树、回表与执行计划的完整链路

举报
yd_246331832 发表于 2026/11/04 03:30:26 2026/11/04
【摘要】 索引不是加速开关:B+树叶子存什么、回表要花多少随机读、优化器按什么算成本,共同决定查询快慢。本文从结构、失效场景到EXPLAIN排查讲完整链路。

很多人在表上加了索引,以为查询就一定快,结果一看执行计划还是全表扫描,或者走了索引却比不走还慢。问题往往不在索引本身,而在于把索引当成了加速开关。实际上索引只是一棵需要被正确使用的数据结构,查询快慢由优化器、数据分布和回表成本共同决定。

以 InnoDB 为例,索引的本质是 B+树。叶子节点按顺序连成链表,非叶子节点只存键和指针。聚簇索引的叶子存整行数据,二级索引的叶子只存索引列和主键。查询先在二级索引树里找到主键,再拿主键去聚簇索引树里找整行,第二步就叫回表。每回表一次,就是一次随机读,返回行数一多,随机读的成本就可能超过顺序全表扫描。

如果查询要的列刚好都在二级索引里,就不用回表,执行计划的 Extra 会出现 Using index,这叫覆盖索引。这就是为什么只取索引列会快很多倍,而 select 星号容易慢:不是索引变强了,而是少了一次到聚簇索引树的查找。写查询时先问一句,我真的需要整行吗,往往比加索引更有效。

MySQL 也不会无条件走索引。它会根据统计信息估算每条路径的成本:扫描行数、随机 IO、排序、回表。估算走索引要回表十万行,而全表扫描顺序读十万行更便宜时,它就会选全表扫描。统计信息过期、数据倾斜严重时,这个估算还会错,于是出现明明有索引却不用的假象。先怀疑统计信息和数据分布,再怀疑索引本身。

索引失效有几种典型情况。对索引列做函数、运算、隐式类型转换时,索引就用不上,比如 varchar 列用数字比较,因为 B+树的有序性被破坏了。LIKE 以百分号开头无法定位左边界,复合索引不满足最左前缀时后面的列也用不上,范围条件之后的列往往失效,OR 连接的条件如果有一边没有索引,也可能退化成全表扫描。这些不是玄学,都是左边界和有序性被破坏的结果。

排序和连接也受索引影响。索引叶子本来就是有序的,刚好能帮 ORDER BY 省掉 filesort。但如果 WHERE 和 ORDER BY 用的不是同一个索引,或者对排序列做了函数处理,还是要额外排序。连接时驱动表选错、被驱动表没有索引,就会变成对每行都全表扫描一次,数据量一放大就是灾难。这时问题不在单表索引,而在连接顺序和驱动表选择。

排查要先看 EXPLAIN,而不是凭感觉加索引。看 type 是 ref、range 还是 ALL,看 key 实际用了哪个索引,看 rows 估算多少行,看 Extra 有没有 Using filesort、Using temporary、Using index。type 从好到坏大概是 const、ref、range、index、ALL,但不要只看 type,要结合 rows 和回表量。再看慢查询日志的 Rows_examined 和 Rows_sent,如果 examined 远大于 sent,说明读了很多用不上的行。

修查询往往比加索引更重要。只取需要的列,避免 select 星号,避免对索引列做运算,把隐式转换改成显式同类型比较,把前导百分号的 LIKE 改成前缀或全文索引,复合索引按查询频率和选择性排好顺序,定期更新统计信息。每次改动都要对照强制索引前后的执行时间和 examined 行数验证,而不是凭感觉加一个索引就收工。

索引也不是免费的。每次写入都要维护所有 B+树,索引越多写越慢、空间越大,还会拖慢更新和删除。读多写少的表可以多建,写多读少的表要克制,长期没被用到的索引应该删掉,用慢日志和性能视图确认它真的没人用,而不是留着以防万一。

所以查询慢很少是单一原因,通常是索引结构、优化器成本估算、回表量、排序和连接共同作用的结果。理解 B+树叶子存什么、优化器按什么算成本、EXPLAIN 每个字段是什么意思,才能从加个索引试试,变成知道为什么慢、改哪里最划算。

【声明】本内容来自华为云开发者社区博主,不代表华为云及华为云开发者社区的观点和立场。转载时必须标注文章的来源(华为云社区)、文章链接、文章作者等基本信息,否则作者和本社区有权追究责任。如果您发现本社区中有涉嫌抄袭的内容,欢迎发送邮件进行举报,并提供相关证据,一经查实,本社区将立刻删除涉嫌侵权内容,举报邮箱: cloudbbs@huaweicloud.com
  • 点赞
  • 收藏
  • 关注作者

评论(0)

0/1000
抱歉,系统识别当前为高风险访问,暂不支持该操作

全部回复

上滑加载中

设置昵称

在此一键设置昵称,即可参与社区互动!

*长度不超过10个汉字或20个英文字符,设置后3个月内不可修改。

*长度不超过10个汉字或20个英文字符,设置后3个月内不可修改。