一、背景知识:什么是联合索引?
在数据库(如 MySQL)中,联合索引(Composite Index)是由多个列组成的索引,例如 KEY idx_b_a (b, a)
表示一个由列 b
和列 a
按顺序组成的索引。联合索引的底层通常基于 B+ 树,其键值按照索引定义的顺序(先 b
,后 a
)排序。
联合索引的特性
- 左前缀原则 :查询条件必须从索引的最左列开始利用,否则无法命中索引。例如:
WHERE b = 1 AND a = 123
能用上(b, a)
。WHERE a = 123
只能部分利用或完全不利用,取决于优化器。
- 存储结构 :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),具体取决于优化器的选择。
- 执行过程 :
- MySQL 优化器发现
a
是(b, a)
的一部分,虽然b
未指定,但索引中每一行都包含a
的值。 - 优化器可以选择扫描整个
(b, a)
索引,逐行检查a
是否等于 123。 - 对于符合条件的记录,通过索引中的主键值回表(若需要
*
中的非索引列)。
- MySQL 优化器发现
- 为什么不是全表扫描?
- 如果表有主键索引和
(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 |
字段解析
- type :
index
- 表示全索引扫描(Index Scan),扫描整个
(b, a)
索引。 - 原因:条件
a = 123
无法利用索引的最左列b
,但仍使用了索引。
- 表示全索引扫描(Index Scan),扫描整个
- possible_keys :
idx_b_a
- 表示优化器考虑使用的索引。
- key :
idx_b_a
- 实际使用的索引。
- rows :
1000
- 估计扫描的行数(整个索引的行数)。
- Extra :
Using where
- 表示在扫描索引后,用
WHERE
条件过滤出a = 123
的行。
- 表示在扫描索引后,用
结论
- 这里是全索引扫描,而不是范围扫描(
range
)或唯一查找(ref
),因为b
未受限。
四、回顾 EXPLAIN 中的常见 type
类型
EXPLAIN
的 type
列反映了查询的访问类型,从高效到低效排序如下:
-
const
- 通过主键或唯一索引精确匹配一行(如
WHERE id = 1
)。 - 效率最高。
- 通过主键或唯一索引精确匹配一行(如
-
eq_ref
- 在联表查询中,通过主键或唯一索引匹配一行。
- 示例:
SELECT * FROM t1 JOIN t2 ON t1.id = t2.id
。
-
ref
- 通过非唯一索引查找匹配的行,可能返回多行。
- 示例:
WHERE a = 123
(a
有普通索引)。
-
range
- 通过索引进行范围扫描。
- 示例:
WHERE a > 100 AND a < 200
。
-
index
- 全索引扫描,扫描整个索引树。
- 示例:本文的
WHERE a = 123
用(b, a)
。
-
ALL
- 全表扫描,逐行检查表数据。
- 示例:无索引可用时。
记忆技巧
- const/ref:精确查找,效率高。
- range:范围查找,部分索引。
- index/ALL :全扫描,
index
用索引,ALL
用表,效率较低。
五、总结与优化建议
-
查询分析结论
- 对于
SELECT * FROM table WHERE a = 123
,索引(b, a)
会导致全索引扫描(type = index
),因为无法利用最左列b
缩小范围。 - 如果数据量大,性能接近全表扫描。
- 对于
-
优化建议
- 如果
a
的查询频率高,考虑单独为a
创建索引(如KEY idx_a (a)
),这样查询会走ref
或range
。 - 如果业务允许,调整查询加上
b
的条件(如WHERE b = xxx AND a = 123
),充分利用(b, a)
。
- 如果