(b,a)索引:Select * from t where a=1:为何Explain后type为Index,而非All?

一、背景知识:什么是联合索引?

在数据库(如 MySQL)中,联合索引(Composite Index)是由多个列组成的索引,例如 KEY idx_b_a (b, a) 表示一个由列 b 和列 a 按顺序组成的索引。联合索引的底层通常基于 B+ 树,其键值按照索引定义的顺序(先 b,后 a)排序。

联合索引的特性
  1. 左前缀原则 :查询条件必须从索引的最左列开始利用,否则无法命中索引。例如:
    • WHERE b = 1 AND a = 123 能用上 (b, a)。
    • WHERE a = 123 只能部分利用或完全不利用,取决于优化器。
  2. 存储结构 :B+ 树叶子节点按 (b, a) 顺序存储,包含索引列和主键值(对于 InnoDB)。

现在,我们来分析 SELECT * FROM table WHERE a = 123 在索引 (b, a) 下的执行过程。


二、查询分析:SELECT * FROM table WHERE a = 123

1. 查询条件与索引匹配
  • 查询条件只有 a = 123,而索引是 (b, a)。
  • 根据左前缀原则,索引的最左列 b 未出现在 WHERE 条件中,因此无法直接通过 b 的值定位到具体的索引范围。
  • 但是,a 是联合索引的一部分,优化器可能会选择利用索引 (b, a),而不是完全忽略它。
2. 是否走全索引扫描?

答案是:可能会走全索引扫描(Index Scan),但不一定是全表扫描(Table Scan),具体取决于优化器的选择。

  • 执行过程 :
    1. MySQL 优化器发现 a 是 (b, a) 的一部分,虽然 b 未指定,但索引中每一行都包含 a 的值。
    2. 优化器可以选择扫描整个 (b, a) 索引,逐行检查 a 是否等于 123。
    3. 对于符合条件的记录,通过索引中的主键值回表(若需要 * 中的非索引列)。
  • 为什么不是全表扫描?
    • 如果表有主键索引和 (b, a) 索引,扫描 (b, a) 索引通常比扫描整个表(全表扫描)更高效,因为索引的数据量通常小于表数据,且 B+ 树的顺序访问性能较高。
  • 是否高效?
    • 不高效。因为没有 b 的条件,相当于遍历整个索引,性能接近于全表扫描,尤其当表数据量很大时。
3. 可能的优化器行为
  • 如果表数据量小,或 (b, a) 是覆盖索引(即 SELECT 只涉及 b 和 a,无需回表),优化器可能倾向于用索引。
  • 如果表很大且选择性低(a = 123 匹配大量行),优化器可能直接选择全表扫描。

三、使用 EXPLAIN 查看执行计划

我们可以用 EXPLAIN 查看 MySQL 的执行计划,以下是分析结果的可能输出和解释:

示例 SQL
sql 复制代码
EXPLAIN SELECT * FROM table WHERE a = 123;
可能的输出
id select_type table type possible_keys key key_len ref rows Extra
1 SIMPLE table index idx_b_a idx_b_a 8 NULL 1000 Using where
字段解析
  1. type :index
    • 表示全索引扫描(Index Scan),扫描整个 (b, a) 索引。
    • 原因:条件 a = 123 无法利用索引的最左列 b,但仍使用了索引。
  2. possible_keys :idx_b_a
    • 表示优化器考虑使用的索引。
  3. key :idx_b_a
    • 实际使用的索引。
  4. rows :1000
    • 估计扫描的行数(整个索引的行数)。
  5. Extra :Using where
    • 表示在扫描索引后,用 WHERE 条件过滤出 a = 123 的行。
结论
  • 这里是全索引扫描,而不是范围扫描(range)或唯一查找(ref),因为 b 未受限。

四、回顾 EXPLAIN 中的常见 type 类型

EXPLAIN 的 type 列反映了查询的访问类型,从高效到低效排序如下:

  1. const

    • 通过主键或唯一索引精确匹配一行(如 WHERE id = 1)。
    • 效率最高。
  2. eq_ref

    • 在联表查询中,通过主键或唯一索引匹配一行。
    • 示例:SELECT * FROM t1 JOIN t2 ON t1.id = t2.id。
  3. ref

    • 通过非唯一索引查找匹配的行,可能返回多行。
    • 示例:WHERE a = 123(a 有普通索引)。
  4. range

    • 通过索引进行范围扫描。
    • 示例:WHERE a > 100 AND a < 200。
  5. index

    • 全索引扫描,扫描整个索引树。
    • 示例:本文的 WHERE a = 123 用 (b, a)。
  6. ALL

    • 全表扫描,逐行检查表数据。
    • 示例:无索引可用时。
记忆技巧
  • const/ref:精确查找,效率高。
  • range:范围查找,部分索引。
  • index/ALL :全扫描,index 用索引,ALL 用表,效率较低。

五、总结与优化建议

  1. 查询分析结论

    • 对于 SELECT * FROM table WHERE a = 123,索引 (b, a) 会导致全索引扫描(type = index),因为无法利用最左列 b 缩小范围。
    • 如果数据量大,性能接近全表扫描。
  2. 优化建议

    • 如果 a 的查询频率高,考虑单独为 a 创建索引(如 KEY idx_a (a)),这样查询会走 ref 或 range。
    • 如果业务允许,调整查询加上 b 的条件(如 WHERE b = xxx AND a = 123),充分利用 (b, a)。
相关推荐
念何架构之路3 小时前
Go reflect包值操作 Value 的读写、可设置性与 interface 布局
开发语言·后端·golang
小林ixn3 小时前
用 FastAPI + SQLAlchemy 从零手搓一个博客系统:一套能落地、能讲清楚的 RESTful 后端
后端·python·fastapi
不识君4 小时前
esl-client 2.0.0 发布:Java 项目接入 FreeSWITCH,从引入到使用
spring boot·后端·开源
牧瀬クリスだ4 小时前
Redis五大数据类型全解析:从入门到实战
java·数据库·redis·后端·缓存
Rain的Java大神之路4 小时前
北漂 13 年,我把 Java 家底钉成了这张知识图谱(全站免费)
java·后端·程序员
Amos_Web4 小时前
通过 Rspack 源码学 Rust(一):`mod`、`pub` 与 `use crate`/`use super`
后端
张彦峰ZYF4 小时前
拆得开不等于该拆开:微服务与模块化单体的边界判据、规模阈值、团队适配与一致性代价
后端·微服务·边界是否稳定·扩展天花板是否到顶·有无小组能独立值班·独立部署的自由会否被用上·这一刀是否切断不可降级的不变量
是江迪呀5 小时前
介绍一个开源项目 —— 烂笔头
前端·后端
captain3765 小时前
Spring Web MVC(2)
java·前端·spring boot·后端·spring·mvc
紧跟先前的步伐5 小时前
Go AI Agent 实战 17|模型适配层设计:让模型可替换
开发语言·人工智能·后端·ai·golang