B+树的排序规则决定了联合索引必须严格遵循最左前缀原则:索引(a,b,c)中,a为第一排序字段,a相同时b才有序,a和b都相同时c才有序;若跳过b直接查a和c,因c在a=1条件下无序,无法定位,只能全量扫描a=1的行后内存过滤。

组合索引字段顺序不是“尽量匹配”,而是“不匹配就失效”——B+树物理结构决定了它只能从左到右逐列比较,跳过任意一列,右侧字段就彻底退出搜索路径。
为什么WHERE a = 1 AND c = 3用不上(a, b, c)索引
因为B+树叶子节点是先按a排序、a相同时再按b排序、a和b都相同时才按c排序。当b缺失时,所有a = 1的记录中c是完全无序的,数据库无法跳转到c = 3的位置,只能扫描全部a = 1的行再内存过滤。
EXPLAIN里会看到:key_len只反映a的字节数,Extra含Using where,说明c没参与索引查找。
-
WHERE a = 1 AND b = 2 AND c = 3→key_len完整,三列全走索引查找 -
WHERE a = 1 AND c = 3→key_len仅含a,c纯靠Server层过滤 -
WHERE b = 2 AND c = 3→key为NULL,直接全表扫描(type: ALL)
ORDER BY顺序错一位就触发Using filesort
索引(user_id, created_at)能加速SELECT ... WHERE user_id = 123 ORDER BY created_at DESC,但换成ORDER BY created_at DESC, user_id ASC或ORDER BY created_at ASC(方向不一致),就会强制额外排序。
原因很直接:B+树只保证user_id = 123子集内created_at有序;一旦user_id本身是范围条件(如user_id > 100),或排序字段顺序/方向与索引定义不严格对齐,数据在磁盘上就不是连续有序的。
- MySQL 5.7 不支持混合方向索引,
INDEX(a ASC, b DESC)实际等价于INDEX(a, b) - MySQL 8.0+ 支持方向声明,但查询中的
ORDER BY必须完全一致(包括ASC/DESC)才能免排序 -
EXPLAIN里只要出现Using filesort,基本可断定排序字段没对齐索引定义
高频等值字段必须放最左,和“区分度高低”无关
别被“高基数字段放前面更高效”的说法带偏。真正决定顺序的是查询模式:哪个字段几乎每次WHERE都出现、且是=或IN?它就必须放第一。
比如status只有'active'/'inactive'两个值,选择性极低,但如果90%查询都带WHERE status = 'active',那它就是索引的锚点——没有它,整个索引就起不来。
-
city有几百个值,但单独查WHERE city = 'shanghai'极少,和status组合后过滤率高达95% → 适合第二位 -
created_at虽然区分度最高,但常带>条件 → 必须放最后,否则后面所有列全废 - 如果既有
WHERE a = ? AND b = ?,又有WHERE b = ? AND c = ?,别强塞进一个索引,建两个:(a,b)和(b,c)
调整顺序等于删索引重建,线上不能硬来
MySQL不支持原地修改联合索引字段顺序。ALTER TABLE ... DROP INDEX + ADD INDEX不是原子操作,期间新索引不可用,还可能锁表。
尤其表体积超过10GB时,直接ALTER TABLE极易引发主从延迟或长事务阻塞。
- MySQL 5.7+ 的
ALGORITHM=INPLACE仅适用于增删索引,不适用于改顺序 - 必须用
pt-online-schema-change或gh-ost做在线变更 - 重建后务必
DROP掉旧索引,冗余索引会拖慢写入性能
最易被忽略的一点:索引顺序是否生效,和SQL里WHERE条件的书写顺序完全无关——优化器会自动重排;真正致命的是索引定义本身有没有把业务查询的“必查等值字段”放在最左位置。


















