联合索引的最左前缀匹配到底怎么生效
MySQL 的联合索引不是“只要字段在索引里就能用”,而是严格按定义顺序逐列匹配。比如建了
INDEX idx_user_status_time (user_id, status, created_at),只有当 WHERE 条件中出现
user_id(或含
user_id的等值条件)时,索引才可能被使用;如果只查
status = 1或
created_at > '2024-01-01',这个索引基本无效。
常见错误是以为“覆盖了查询字段就行”,结果
EXPLAIN显示
type: ALL或
key: NULL。真正起效的组合包括:
WHERE user_id = 123
WHERE user_id = 123 AND status = 1
WHERE user_id = 123 AND status IN (1,2) AND created_at > '2024-01-01'
WHERE user_id = 123 AND status = 1 ORDER BY created_at LIMIT 10(可用于排序优化)
但
WHERE status = 1 AND created_at > '2024-01-01'就无法走该索引——因为跳过了最左列
user_id。
等值查询 + 范围查询的顺序不能乱
在联合索引中,等值条件(
=、
IN)应尽量放在左侧,范围条件(
>、
、<code>BETWEEN、
LIKE 'abc%')靠右。一旦出现范围查询,其右侧所有字段就无法用于索引查找(但仍可能参与索引覆盖)。
例如索引
(a, b, c):
WHERE a = 1 AND b > 10 AND c = 5→ 只能用上
a和
b,
c不参与查找(但若
SELECT c,仍可从索引中直接读取,即“覆盖索引”)
WHERE a = 1 AND b = 5 AND c > 100→ 三列都可用于查找,效率最高
WHERE a > 1 AND b = 5→ 实际只用到
a,
b完全失效
所以建索引前要预判高频查询模式:如果常查
user_id = ? AND created_at > ?,那
(user_id, created_at)比
(created_at, user_id)合理得多。
什么时候该拆联合索引,而不是堆字段
联合索引不是字段越多越好。每多一列,索引体积增大、写入开销上升、缓存命中率下降。尤其当某列选择性极低(如
status只有 0/1)、或很少单独出现在 WHERE 中时,加进去反而拖累整体性能。
判断是否保留某列进联合索引,看三点:
该列是否在 WHERE 中与左侧列构成高频等值组合? 该列是否显著提升rows预估精度(用
EXPLAIN FORMAT=JSON查
used_columns和
range_analysis)? 该列是否让常见查询实现“索引覆盖”(即
Extra出现
Using index)?
比如表有
user_id、
tenant_id、
status、
created_at,若 90% 查询都是
tenant_id = ? AND user_id = ?,那优先建
(tenant_id, user_id);若还有大量
SELECT status, created_at也走这个条件,再考虑扩展为
(tenant_id, user_id, status, created_at)——但得先确认
status和
created_at的区分度够不够支撑这额外开销。
ORDER BY 和 GROUP BY 如何借力联合索引
MySQL 能利用联合索引避免文件排序(
Using filesort),前提是 ORDER BY 字段顺序与索引最左前缀一致,且不混用 ASC/DESC(8.0+ 支持混合,但需显式声明)。
例如索引
(user_id, created_at):
ORDER BY user_id, created_at→ ✅ 无 filesort
ORDER BY user_id DESC, created_at DESC→ ✅(需索引定义为
KEY idx (user_id DESC, created_at DESC))
ORDER BY user_id ASC, created_at DESC→ ❌ filesort(除非 MySQL 8.0+ 且索引明确定义了混合方向)
ORDER BY created_at→ ❌ 无法利用(跳过最左列)
GROUP BY 同理。如果常做
GROUP BY user_id, status并聚合统计,建
(user_id, status)索引后,
EXPLAIN中的
Extra会显示
Using index for group-by,比临时表快得多。
EXPLAIN SELECT user_id, status, COUNT(*) FROM orders WHERE user_id IN (101, 102, 103) GROUP BY user_id, status;
实际调优中最容易被忽略的是:索引字段顺序必须和 GROUP BY / ORDER BY 的列顺序完全一致,差一个位置,优化器就放弃使用。
