联合索引必须遵循最左前缀原则,跳过最左列将导致全表扫描;范围查询后右侧列失效;ORM动态SQL易错序;字符集或类型不一致会隐式转换使索引失效;索引顺序应按查询频次、过滤强度和等值条件优化。

联合索引没按最左前缀查,直接退化成全表扫描
复合索引不是“只要字段在里面就能用”,它像字典——先按第一列排,相同再按第二列,以此类推。一旦 WHERE 条件跳过最左列(比如索引是 (name, age, city),却只写 WHERE age = 25),MySQL 就没法从索引树根开始定位,只能扫全表。
- 典型错误:
SELECT * FROM users WHERE age > 20 AND city = '杭州',而索引是KEY idx_name_age_city (name, age, city) - 真正生效的写法必须包含最左列:
WHERE name = '张三' AND age > 20(此时city虽未用上,但name和age都走索引) - 如果业务真要高频查
age,别硬凑,单独建INDEX idx_age (age)更实在
范围查询后,右边的索引列自动“失能”
在联合索引中,一旦某列用了 >、、BETWEEN 这类范围条件,它右边所有列就只能回表过滤,不再参与索引查找。
- 例子:索引为
(status, create_time, user_id),执行WHERE status = 'paid' AND create_time > '2025-01-01'→user_id完全不走索引 - 想让
user_id也进索引?把等值列往前放:INDEX (status, user_id, create_time),再查WHERE status = 'paid' AND user_id = 123 AND create_time > '2025-01-01' - 注意:MySQL 8.0.13+ 支持“索引跳跃扫描”(Loose Index Scan),但只在最左列高区分度时可能触发,不能当稳定方案依赖
ORM 拼 SQL 时字段顺序错位,人眼难发现
很多开发者用 MyBatis、JPA 等框架,写动态 SQL 或 QueryDSL 时,条件顺序由代码逻辑控制。稍不注意,name 条件被 if 判断跳过,只剩 age 和 city,复合索引瞬间失效。
- 排查方法:在日志里开
show_sql=true+log4j.level.com.xxx.mapper=DEBUG,抓出真实执行的 SQL,再用EXPLAIN验证 - 安全做法:对高频组合查询,宁可多建一个覆盖索引,比如
INDEX idx_age_city (age, city),也不强求复用主复合索引 - 特别警惕分页场景:
ORDER BY age LIMIT 10却没WHERE name = ?—— 即使有(name, age)索引,排序也无法利用
字段类型或字符集不一致,悄悄让最左列“失效”
你以为写了最左列,但 MySQL 在底层做了隐式转换:比如索引列是 utf8mb4_bin,而参数传的是 utf8mb4_general_ci;或者字段是 VARCHAR,你传了数字没加引号——这时最左列虽然出现,但匹配过程已绕过索引结构。
- 常见表现:
EXPLAIN显示type=ALL或key=NULL,但 WHERE 里明明有索引第一列 - 验证方式:执行
SHOW CREATE TABLE xxx查字段定义,再对比应用层传参的类型和字符集 - 修复动作:统一字符集(推荐全库
utf8mb4_unicode_ci),字符串参数务必加单引号,Java 侧用String接收而非Long
复合索引顺序不是“按业务语义排”,而是按查询频次+过滤强度+是否等值来排。最左列选错,后面全白搭;更麻烦的是,这种问题在线上跑几天都未必暴露——数据量小的时候全表扫描也快,一到大促就卡死。










