优化子查询中GROUP BY性能需先检查索引是否覆盖分组字段,执行计划出现Using temporary; Using filesort即表明未走索引;应创建(taskUniqueId, reportTime)等联合索引,分组字段在前、聚合字段在后,并将WHERE条件字段纳入索引;避免全量分组再过滤,须将WHERE条件下推至子查询内,且仅返回必要字段;取每组最新行推荐用ROW_NUMBER()窗口函数而非GROUP BY硬扛。

子查询里GROUP BY慢,先看索引有没有覆盖分组字段
执行计划里出现 Using temporary; Using filesort 就是典型信号:数据库没走索引,被迫建临时表+排序。GROUP BY 本身不慢,慢在它得先拿到有序数据——而索引能提供这个顺序。
比如查每个 taskUniqueId 最新日志时间:SELECT taskUniqueId, MAX(reportTime) FROM task_log_info GROUP BY taskUniqueId,如果只有 id 主键索引,但没在 (taskUniqueId, reportTime) 上建联合索引,就会全表扫描。
- 优先建联合索引:
CREATE INDEX idx_task_report ON task_log_info (taskUniqueId, reportTime);单列索引效果有限 - 注意字段顺序:分组字段放前面(
taskUniqueId),排序/聚合字段放后面(reportTime) - WHERE 条件里的字段也尽量纳入联合索引,比如加个
status,就变成(taskUniqueId, status, reportTime)
外层JOIN子查询结果时,避免“全量分组再过滤”
常见写法:SELECT * FROM orders JOIN (SELECT user_id, SUM(amount) FROM orders GROUP BY user_id) t USING(user_id),看着没问题,但如果外层加了 WHERE create_date > '2026-01-01',子查询却没下推条件,就会对全部历史订单分组,浪费巨大。
- 把过滤条件明确写进子查询:
SELECT user_id, SUM(amount) FROM orders WHERE create_date > '2026-01-01' GROUP BY user_id - 若需分页,
LIMIT必须放在子查询里,否则外层仍要处理全部分组结果 - 子查询只返回必要字段(如
user_id和聚合值),别带*或多余列,减少传输和JOIN开销
想取每组最新一行?别用GROUP BY硬扛,窗口函数更稳
用 GROUP BY user_id, MAX(created_at) 看似能取最新时间,但无法保证对应的是哪条记录的 order_no 或 status——它们可能来自不同行。这是语义错误,不是性能问题。
- MySQL 8.0+ / PostgreSQL 推荐用:
ROW_NUMBER() OVER (PARTITION BY user_id ORDER BY created_at DESC),外层筛rn = 1 - MySQL 5.7 只能用相关子查询:
SELECT * FROM orders o1 WHERE created_at = (SELECT MAX(created_at) FROM orders o2 WHERE o2.user_id = o1.user_id),但要注意并列时间导致重复 - 别在子查询里
GROUP BY + SELECT *,严格模式直接报错;即使兼容模式侥幸通过,结果也不可靠
GROUP BY后还要二次聚合?必须拆成两层,且注意字段对齐
MAX(AVG(price)) 是非法语法,所有数据库都拒绝。这不是写法技巧问题,而是SQL执行顺序决定的:聚合函数不能嵌套在同一层级。
- 内层按维度产出聚合值:
SELECT region, AVG(price) AS avg_price FROM sales GROUP BY region - 外层再加工:
SELECT MAX(avg_price) FROM (内层SQL) AS region_avg - 外层若要保留
region字段,就得同时出现在外层SELECT和GROUP BY中,否则会报错Expression #1 of SELECT list is not in GROUP BY clause - HAVING 放内层筛分组结果,WHERE 放内层筛原始数据,别混用
实际调优中最容易被忽略的点是:子查询的 GROUP BY 是否真正收敛到业务唯一键。比如按 user_id 分组,但子查询里漏了 status = 'active' 这个约束,结果就可能返回多个相同 user_id,外层 JOIN 一关联,数据就翻倍。验证方法很简单——把子查询单独跑一遍,SELECT user_id, COUNT(*) GROUP BY user_id,看有没有 count > 1 的行。










