因为最左前缀匹配要求查询必须从索引最左列开始连续使用,(status, user_id)中若只查user_id则索引完全失效;且status低区分度时放最左会大幅增加B+树扫描量,拖慢所有依赖该索引的查询。

为什么 (status, user_id) 比 (user_id, status) 更容易失效
不是字段本身不能用,而是 MySQL 的最左前缀匹配规则强制要求:查询必须从索引最左侧列开始连续使用,且首列需为确定等值条件。如果索引是 (status, user_id),而查询只写了 WHERE user_id = 123,那整个索引完全不会被选中。
更隐蔽的问题是:当 status 区分度极低(比如只有 'active'/'inactive' 两类),它放在最左会拖慢所有依赖该索引的等值查询——因为 B+ 树第一层就要遍历大量重复值节点,导致索引页扫描量陡增。
- 执行
SELECT COUNT(DISTINCT status) / COUNT(*) FROM orders,结果低于0.03就算低区分度 - 高频查询若固定带
user_id条件,user_id应优先放最左 - 若必须按
status过滤后再查user_id,且status值固定(如只查'active'),可接受其在左;否则不建议
EXPLAIN 显示 key 不为空 ≠ 查询真的快
EXPLAIN 中 key 列非空,只说明优化器选了这个索引,不代表执行效率高。低区分度字段在索引左侧时,常见两种隐性开销:
-
rows_examined显著高于实际返回行数 → 索引扫描了大量无效分支 -
Handler_read_rnd_next暴涨(远超Handler_read_next)→ 回表严重,说明索引覆盖不足 - 写锁范围扩大:比如
UPDATE ... WHERE status = 'pending'可能锁住上万行,而非精准定位
实操建议:用慢日志 + pt-query-digest 观察真实 Query_time 和 Rows_examined,比单看 EXPLAIN 更可靠。
什么时候可以把低区分度字段放在索引右侧
低区分度字段不是不能进索引,而是不能当“领头羊”。把它放在右侧,主要服务于两个目的:排序/分组加速,或辅助覆盖查询。
- 场景一:查询带
ORDER BY user_id, status,且user_id是高区分度主键 → 建(user_id, status)可避免Using filesort - 场景二:高频查询固定返回
SELECT id, user_id, status→(user_id, status, id)是覆盖索引,彻底消除回表 - 场景三:需要
GROUP BY status但总要带上user_id过滤 →(user_id, status)让分组在索引内完成,不依赖临时表
注意:status 在右侧时,它无法参与 WHERE 条件的快速过滤,仅起“二次筛选”或“有序输出”作用。
调整顺序前必须验证的三件事
直接 DROP INDEX 再 CREATE INDEX 有风险,尤其在线上大表。动手前确认:
- 当前索引是否被其他查询依赖?用
SELECT * FROM information_schema.STATISTICS WHERE table_name = 'orders' AND index_name = 'idx_status_uid'查引用情况 - 新顺序是否覆盖现有慢查询?用
EXPLAIN FORMAT=JSON对比key_length、rows、filtered字段变化 - 写入压力是否敏感?
ALTER TABLE ... ADD INDEX在 MySQL 5.7+ 默认支持 ALGORITHM=INPLACE,但仍会加元数据锁,避开业务高峰
真正容易被忽略的点:低区分度字段一旦进了联合索引,它就不再是“可有可无”的配角——它会永久增加索引体积、拖慢 INSERT/UPDATE,并影响所有走该索引的查询路径。换顺序不是微调,是重新定义索引职责。











