因为函数会让索引失效,MySQL无法用created_at上的索引直接定位分组键,只能全表扫描+内存排序或磁盘临时表;执行计划出现Using temporary; Using filesort即确认索引未被使用。

GROUP BY中用了DATE(created_at)为什么变慢?
因为函数会让索引失效,MySQL无法用created_at上的索引直接定位分组键,只能全表扫描+内存排序或磁盘临时表。执行计划里一旦出现Using temporary; Using filesort,基本就确认走不了索引了。
常见错误写法:GROUP BY DATE(created_at)、GROUP BY YEAR(month)、GROUP BY CONCAT(first_name, ' ', last_name)。
- 优先改用范围条件提前过滤:比如想查“2024年每天订单量”,别写
GROUP BY DATE(order_time),而是先用WHERE order_time BETWEEN '2024-01-01' AND '2024-12-31'缩小数据集,再配合(order_time)索引做松散索引扫描 - 必要时加生成列并建索引:MySQL 5.7+ 支持存储生成列,例如
ALTER TABLE orders ADD COLUMN order_date DATE AS (DATE(order_time)) STORED,再对order_date建索引 - 避免在高基数字段上套函数分组:比如
GROUP BY SUBSTRING(phone, 1, 3)不如提前把号段存成独立字段
WHERE和GROUP BY都用到同一个字段,索引怎么建?
必须把WHERE条件字段放在复合索引最左侧,GROUP BY字段紧随其后——顺序不能反。MySQL的索引最左前缀原则决定了它只能跳过右侧字段,不能跳过左侧。
例如查询:SELECT user_id, COUNT(*) FROM logs WHERE status = 'success' GROUP BY user_id,正确索引是(status, user_id),不是(user_id, status)。
- 如果还有
SELECT里的聚合字段(如SUM(amount)),把它们也加到索引末尾,构成覆盖索引,避免回表 - 多个
WHERE条件时,把区分度高的字段放前面:比如type只有3个值,region有200个值,那索引应为(region, type, user_id) - 注意:MySQL 8.0+ 支持函数索引,但仅限于表达式本身可确定性计算(如
UPPER(email)),且需显式声明;老版本不支持,别依赖
GROUP BY多字段时顺序影响大吗?
影响非常直接:索引字段顺序必须和GROUP BY子句中的列顺序严格一致,否则无法触发松散索引扫描(Loose Index Scan),只能退化为紧凑扫描甚至临时表。
比如GROUP BY a, b, c,索引必须是(a, b, c);若建的是(a, c, b),MySQL大概率不用这个索引做分组。
- 如果查询还带
ORDER BY a, b,那(a, b, c)索引还能顺便免去排序步骤 - 如果实际只按
a分组,但索引是(a, b, c),依然能用上——只要前缀匹配就行 - 别为了“看起来全面”而堆砌字段:索引越宽,写入开销越大,维护成本越高;只保留真正参与
WHERE或GROUP BY的列
为什么加了索引还是慢?检查这几个点
索引存在 ≠ 被使用。很多问题出在隐式转换、统计信息过期或配置限制上。
- 检查
EXPLAIN输出的key列是否为空,rows是否远大于预期——如果是,说明优化器放弃了你的索引 - 确认字段类型是否一致:比如
user_id是BIGINT,但WHERE里写了'123'(字符串),会触发隐式转换,索引失效 - 运行
ANALYZE TABLE table_name更新统计信息,尤其在大批量导入后 - 查
tmp_table_size和max_heap_table_size是否太小:分组结果超出内存就会落盘,I/O飙升;但盲目调大可能挤占其他连接内存
真正卡住的地方往往不是语法,而是索引能不能被选中、数据能不能被提前过滤、以及分组键是不是足够“轻”。别只盯着GROUP BY本身,得把它放进整个查询链路里看。

















