外观
多个单列索引和联合索引的区别
约 1425 字大约 5 分钟
2025-06-19
本文基于 MySQL 5.7.14 版本进行测试,通过实际案例对比联合索引与单列索引的使用效果,深入理解最左前缀原则。
测试环境搭建
创建一张 10 万数据的测试表,包含联合索引 (t1, t2, t3):
CREATE TABLE `test_10w` (
`id` int(11) NOT NULL AUTO_INCREMENT,
`t1` int(11) NOT NULL DEFAULT '0',
`t2` varchar(255) NOT NULL DEFAULT '',
`t3` int(11) NOT NULL DEFAULT '0',
`t4` varchar(255) NOT NULL DEFAULT '',
PRIMARY KEY (`id`),
KEY `idx_t1_t2_t3` (`t1`,`t2`,`t3`) USING BTREE
) ENGINE=InnoDB DEFAULT CHARSET=utf8mb4;联合索引测试
测试 1:查询条件为 t1
EXPLAIN SELECT * FROM test_10w WHERE t1 = 1000;结果:联合索引有效,使用 idx_t1_t2_t3 索引,type=ref。
测试 2:查询条件为 t2(跳过 t1)
EXPLAIN SELECT * FROM test_10w WHERE t2 = 'a1000';结果:联合索引无效,全表扫描(type=ALL)。
测试 3:查询条件为 t3(跳过 t1、t2)
EXPLAIN SELECT * FROM test_10w WHERE t3 = 1000;结果:联合索引无效,全表扫描(type=ALL)。
测试 4:查询条件为 t1 AND t2
EXPLAIN SELECT * FROM test_10w WHERE t1 = 1000 AND t2 = 'a1000';结果:联合索引有效,key_len=771(t1 的 4 字节 + t2 的 767 字节)。
测试 5:查询条件为 t2 AND t1(顺序调换)
EXPLAIN SELECT * FROM test_10w WHERE t2 = 'a1000' AND t1 = 1000;结果:联合索引依然有效。MySQL 优化器会自动调整 WHERE 条件的顺序,不影响索引使用。
测试 6:查询条件为 t1 OR t2
EXPLAIN SELECT * FROM test_10w WHERE t1 = 1000 OR t2 = 'a1000';结果:联合索引无效,全表扫描(type=ALL)。
说明:
OR条件会导致联合索引失效,除非OR两侧的条件都包含联合索引的最左前缀列。
测试 7:查询条件为 t1 AND t3
EXPLAIN SELECT * FROM test_10w WHERE t1 = 1000 AND t3 = 1000;结果:联合索引部分有效,Using index condition(ICP,索引条件下推)。只使用了 t1 的索引,t3 需要通过回表后进一步过滤。
测试 8:查询条件为 t2 AND t3
EXPLAIN SELECT * FROM test_10w WHERE t2 = 'a1000' AND t3 = 1000;结果:联合索引无效,全表扫描(type=ALL)。跳过了最左列 t1。
测试 9:查询条件为 t1 AND t2 AND t3
EXPLAIN SELECT * FROM test_10w WHERE t1 = 1000 AND t2 = 'a1000' AND t3 = 1000;结果:联合索引完全有效,key_len=775(三列全部命中)。
单列索引测试
将上面的联合索引改为三个独立的单列索引后测试:
测试 1:查询条件为 t1 AND t2 AND t3
EXPLAIN SELECT * FROM test_10w WHERE t1 = 1000 AND t2 = 'a1000' AND t3 = 1000;结果:只有 t1 的单列索引生效(key=t1),t2 和 t3 的索引未被使用。
说明:MySQL 查询优化器在多索引可选时会选择一个最优的索引,其余索引不会被联合使用。
测试 2:查询条件为 t2 AND t3
EXPLAIN SELECT * FROM test_10w WHERE t2 = 'a1000' AND t3 = 1000;结果:只有 t2 的单列索引生效,t3 未被使用。
测试 3:查询条件为 t1 OR t2
EXPLAIN SELECT * FROM test_10w WHERE t1 = 1000 OR t2 = 'a1000';结果:使用了 index_merge(索引合并),type=index_merge,key=t1,t2,通过 Using union(t1,t2) 合并两个单列索引的结果。
说明:
OR条件下,单列索引可以通过索引合并生效,但性能通常不如联合索引。
结论
通俗理解
复合索引的结构与电话簿类似:电话簿首先按姓氏排序,然后在相同姓氏内按名字排序。如果你知道姓氏,可以快速定位;如果知道姓氏和名字,定位更精确;但如果只知道名字不知道姓氏,电话簿就派不上用场了。
核心要点
联合索引 vs 多个单列索引:多条件查询时,多个单列索引只会生效一个(通常是优化器认为最优的那个),而联合索引可以同时利用多列的筛选能力。
最左前缀原则:联合索引 (a, b, c) 相当于创建了以下索引的组合:
- (a) — 单列索引
- (a, b) — 双列索引
- (a, b, c) — 三列索引
因此,只有以最左列开头的连续列组合才能命中索引。
索引生效的组合:
查询条件 联合索引 说明 WHERE a✅ 命中 WHERE a AND b✅ 命中 WHERE a AND b AND c✅ 命中 WHERE a AND c⚠️ 仅 a 命中(ICP) WHERE b❌ 未命中 WHERE b AND c❌ 未命中 WHERE a OR b❌ 未命中
最佳实践
- 索引列顺序:将使用频率最高、区分度最大的列放在联合索引的最左边。
- 范围查询:如果最左列是范围查询(
>、<、BETWEEN),范围查询右侧的列将无法使用索引。此时应考虑单独为范围查询列建立索引。 - 避免冗余索引:当存在联合索引 (a, b, c) 时,不需要再单独创建 (a) 和 (a, b) 的索引。
- 数据量小的字段:区分度低的字段(如性别)不适合单独建索引。
- OR 条件:
WHERE中使用OR时,所有参与的列都必须是索引列,否则索引失效。
思考题
当表中同时存在联合索引和单列索引时,MySQL 如何选择使用哪个索引?
这涉及 MySQL 的查询优化器(Optimizer)策略。当存在多条索引可选时,MySQL 会根据统计信息估算各索引的执行成本,选择成本最低的方案。需要注意的是,WHERE 条件中列的书写顺序对优化器没有影响,真正决定性能的是是否命中索引以及命中索引的效率。
