




联合索引必须遵循最左前缀原则,跳过最左列将导致全表扫描;范围查询后右侧列失效;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'
很多开发者用 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
复合索引顺序不是“按业务语义排”,而是按查询频次+过滤强度+是否等值来
