MySQL索引调优之索引顺序是否应该“匹配查询书写顺序”?
✅ 结论先行:
❌ 不会更好,反而可能更差!
你原来的索引顺序是:
idx1: (res_speciality_id, ph_status, del_flag, is_delete, manage_stage)
你想改成:
idx2: (res_speciality_id, manage_stage, ph_status, del_flag, is_delete)
👉 推荐保持原顺序(idx1
),原因如下:
🔍 一、索引顺序的核心原则:选择性 + 等值 vs 范围
复合索引的设计原则是:
把“高选择性”的等值字段放前面,范围字段(如
IN
,>
,<
)尽量往后放
你的字段分析:
字段 | 类型 | 选择性 | 是否等值 | 建议位置 |
---|---|---|---|---|
res_speciality_id | = '231' | 高(过滤90%+) | ✅ 等值 | 第1位 |
ph_status | = '2' | 中 | ✅ 等值 | 第2位 |
del_flag | = '1' | 低 | ✅ 等值 | 第3位 |
is_delete | = '0' | 低 | ✅ 等值 | 第4位 |
manage_stage | IN (1,2) | 中 | ⚠️ 范围 | 最后一位 ✅ |
✅ 正确顺序(原索引):
(res_speciality_id, ph_status, del_flag, is_delete, manage_stage)
- 前4个都是等值条件,可以连续使用索引
- 最后一个是范围条件,放在最后是最佳实践
❌ 你提议的顺序:
(res_speciality_id, manage_stage, ph_status, del_flag, is_delete)
- 第2位就是
IN (1,2)
范围查询 - 一旦遇到范围查询,后面的字段(
ph_status
,del_flag
,is_delete
)就无法使用索引了!
🔥 这是关键!MySQL 在复合索引中,范围查询之后的字段无法再使用索引进行查找
🔍 二、为什么 manage_stage IN (1,2)
是“范围查询”?
虽然 IN (1,2)
看起来像等值,但在 MySQL 中,它被当作范围扫描(range scan)。
一旦使用 IN
,索引的后续字段只能用于“索引条件下推(ICP)”,而不能用于“索引定位”。
🔍 三、举个例子说明
假设你用新索引:
(res_speciality_id, manage_stage, ph_status, del_flag, is_delete)
查询:
WHERE res_speciality_id = '231'AND manage_stage IN (1, 2)AND ph_status = '2' -- ❌ 这个字段无法用于索引定位!
MySQL 的执行过程:
- 用
res_speciality_id = '231'
定位 - 在这个范围内,扫描所有
manage_stage IN (1,2)
的记录 - 对于每一条匹配的记录,再检查
ph_status = '2'
是否成立- 但
ph_status
已经在索引中,只是不能用于“跳过”数据 - 它只能通过 ICP 在索引层过滤,但无法减少索引扫描范围
- 但
→ 相比之下,原索引能用 ph_status
进一步缩小搜索范围。
🔍 四、EXPLAIN 验证预期
索引顺序 | key_len | 能使用的字段 | 说明 |
---|---|---|---|
(res, ph, del, is_del, m_stage) | 较长 | 全部5个字段 | m_stage 在最后,前4个等值字段都能用于定位 |
(res, m_stage, ph, del, is_del) | 相同 | 只有前2个用于定位,后3个只能ICP | m_stage 是范围,后面的字段“失效” |
✅ 五、你的“查询顺序一致”想法为什么不对?
你看到查询是:
AND res_speciality_id = '231'
AND manage_stage IN (1, 2)
AND ph_status = '2'
...
所以觉得索引也该这么排。
但 MySQL 优化器不看 SQL 书写顺序,它会自动重排条件,按索引定义顺序去匹配最左前缀。
所以:
- 索引顺序决定执行路径
- SQL 书写顺序无关紧要
✅ 六、最佳实践总结
✅ 正确的复合索引设计原则:
- 等值字段(
=
)放前面,按选择性从高到低排序 - 范围字段(
IN
,>
,<
,BETWEEN
)放最后 - 不要把范围字段插在中间,否则后面的字段无法用于索引定位
✅ 你的原索引是正确的:
(res_speciality_id, ph_status, del_flag, is_delete, manage_stage)
- ✅ 高选择性等值字段在前
- ✅ 所有等值字段连续
- ✅ 范围字段
manage_stage IN (1,2)
在最后
✅ 七、建议
✅ 保持原索引不变,不要把 manage_stage
提前。
如果你真想验证,可以:
- 创建新索引
- 用
EXPLAIN
对比key_len
和rows
- 你会发现原索引的
rows
更小或相等,但更稳定
🎯 总结一句话:
索引设计应基于“数据访问模式”和“字段类型”,而不是 SQL 的书写顺序。