日常写 SQL,大家都会使用 WHERE 和 GROUP BY,但真正理解它们如何一起使用索引,并不是每个人都能答得清楚。
多数教程讲的只是“最左前缀原则”,却没讲清楚字段不连续的情况会怎样影响索引命中。
今天我们就系统聊一聊,并揭开这个大家经常忽略的性能陷阱。
一句话概括核心原则:
WHERE 字段 + GROUP BY 字段 必须在一个联合索引中,顺序必须连续命中!中间字段断了,后面字段就白搭。
基础回顾:什么是最左前缀匹配?
假设你有这样的联合索引:
INDEX idx (a, b, c)
这个索引可以支持的查询顺序是:
- WHERE a = ?
- WHERE a = ? AND b = ?
- WHERE a = ? AND b = ? AND c = ?
- GROUP BY a
- GROUP BY a, b
- GROUP BY c(前缀断了)
理想示例:字段顺序连续
表结构
CREATE TABLE orders ( id INT PRIMARY KEY, user_id INT, status TINYINT, created_at DATE, INDEX idx (user_id, status, created_at) );
查询
SELECT created_at, COUNT(*) FROM orders WHERE user_id = 123 AND status = 1 GROUP BY created_at;
为什么能命中索引?
- user_id → 第1列
- status → 第2列
- GROUP BY created_at → 第3列
字段连续出现且顺序匹配索引定义,满足最左前缀,MySQL 可以直接利用索引来过滤和分组。
执行计划
Extra: Using where; Using index for group-by
说明 MySQL 利用了索引完成了分组,无需临时表,也不用排序,效率非常高。
被忽略的陷阱:字段中间“断了”
查询:
SELECT created_at, COUNT(*) FROM orders WHERE user_id = 123 GROUP BY created_at;
- 使用了 user_id(第1列)
- 跳过了 status
- 然后用 created_at(第3列)
出现什么问题?
MySQL 虽然可以使用 user_id 过滤数据,但无法跳过 status 去用 created_at 排序或分组。因为:
联合索引必须从最左连续命中,如果中间字段被跳过,后面字段无法使用索引!
执行计划:
Extra: Using where; Using temporary; Using filesort
性能大大下降,MySQL 需要扫描数据,排序,甚至使用临时表。
索引设计建议:围绕查询结构定索引顺序
查询结构 | 推荐索引顺序 |
WHERE user_id AND status GROUP BY created_at | INDEX(user_id, status, created_at) |
WHERE user_id GROUP BY created_at | INDEX(user_id, created_at) |
WHERE company_id GROUP BY dept_id | INDEX(company_id, dept_id) |
小技巧:将 WHERE 最常用、区分度高的字段放前面,GROUP BY 字段跟在后面,组合成联合索引。
常见误区总结
误区 | 结果 |
WHERE 和 GROUP BY 字段分别用了不同索引 | 无法联合使用,GROUP BY 需排序 |
GROUP BY 字段在索引中,但前面字段没用上 | 后面的字段命不中 |
索引字段使用了函数,比如 DATE(created_at) | 索引失效 |
用了 ORDER BY 与索引方向冲突 | 可能排序失效 |
结语:一句话记住这个原则
在 MySQL 中,WHERE 和 GROUP BY 想一起用上索引,字段必须“挨着站队”,中间不能漏人。
如果你看到这里,已经打通了 MySQL 索引优化的关键一环。不妨回头检查一下你线上用的 SQL,是不是也踩了“字段断裂”的坑?
欢迎点赞、收藏、转发,别忘了关注 @一起学习MySQL,我们下次再见!