You need to enable JavaScript to run this app.
优惠活动
大模型
产品
解决方案
定价
更多

优化MySQL分组查询:获取分组内最高grade的完整行

高效获取用户关卡最佳成绩的SQL优化方案

原查询性能问题分析

原查询使用相关子查询,外层每一行数据都会触发一次子查询计算对应分组的最高grade,大数据集下重复计算量极大,导致耗时超3000秒;另外外层的GROUP BY p.user_id, p.step_id属于冗余操作(甚至不符合SQL标准,因为SELECT了所有列但只按两个字段分组),会额外消耗资源。

你改写的内连接查询错误原因

子查询中GROUP BY p2.user_id, p2.step_id但同时SELECT了p2.id,这会导致数据库随机返回分组内的某一个id,而这个id和MAX(p2.grade)没有对应关系,最终关联得到的行自然不是每组grade最高的记录,结果集也就和原查询不一致。

正确的高效查询方案

以下两种方案均能保证结果与原查询一致,且性能远优于原查询:

方案1:保留所有同最高grade的记录(与原查询逻辑完全匹配)

使用RANK()窗口函数,同一(user_id, step_id)分组内grade相同的记录会被全部保留:

SELECT *
FROM (
    SELECT 
        *,
        -- 按用户+关卡分组,按grade降序排名,同grade排名相同
        RANK() OVER (PARTITION BY user_id, step_id ORDER BY grade DESC) AS rank_num
    FROM plays
    WHERE user_id = 24 
      AND time_started BETWEEN '2020-08-01' AND '2022-09-27'
) ranked_plays
WHERE rank_num = 1
ORDER BY time_started ASC;

方案2:同最高grade时仅保留最早的记录

如果需要在同最高grade的记录中只保留一条(比如最早开始的),使用ROW_NUMBER()替代RANK():

SELECT *
FROM (
    SELECT 
        *,
        -- 按用户+关卡分组,先按grade降序,再按开始时间升序排序,生成唯一行号
        ROW_NUMBER() OVER (PARTITION BY user_id, step_id ORDER BY grade DESC, time_started ASC) AS row_num
    FROM plays
    WHERE user_id = 24 
      AND time_started BETWEEN '2020-08-01' AND '2022-09-27'
) numbered_plays
WHERE row_num = 1
ORDER BY time_started ASC;

性能提升关键:添加复合索引

创建覆盖查询所有条件的复合索引,让数据库无需回表即可获取所需数据:

CREATE INDEX idx_plays_user_time_step_grade ON plays(user_id, time_started, step_id, grade);

这个索引包含了过滤条件user_id、time_started,分组字段step_id,以及排序字段grade,能大幅减少查询的IO开销。

内容的提问来源于stack exchange,提问作者Saeven

相关产品推荐
方舟 Agent Plan

超全模态模型 × Harness 升级,最新支持 Deepseek-V4.1-Flash、GLM-5.3 系列、Doubao-Seedream-5.0-pro、Kimi-K3 (部分), 限时 9.9 元起

最近更新时间:2026.08.17 18:35:31