GROUP BY常比ROW_NUMBER快因具备“提前终止”能力,可借索引快速定位每组最大值,仅扫描部分数据即满足LIMIT;而ROW_NUMBER需全量分组、排序、编号后过滤,哪怕只取1条也须处理全部20万行。

没有绝对更快,只看场景:查少量数据(如 LIMIT 10)时 GROUP BY 通常快得多;查全量或需保留明细时 ROW_NUMBER 是唯一选择。
查前 N 条时 GROUP BY 为什么常比 ROW_NUMBER 快?
关键在「提前终止」能力——GROUP BY 子查询可以利用索引快速定位每组最大值,数据库可能只扫描部分数据就凑够 LIMIT 数量;而 ROW_NUMBER 必须先完成全量分组 + 排序 + 编号,再过滤,哪怕只要 1 条也得扫完 20 万行。
- 常见错误现象:
ROW_NUMBER() OVER (PARTITION BY psn_no ORDER BY brdy DESC)在无索引时会触发Using filesort,磁盘 IO 拉垮性能 - 使用场景:想取“每个用户最新一笔订单”,且只要前 10 条结果
- 性能影响:20 万数据下,
GROUP BY + IN子查询耗时可能仅 50ms,同条件ROW_NUMBER达 600ms+ - 实操建议:给
(psn_no, brdy)建联合索引,GROUP BY才能高效走索引下推
ROW_NUMBER 不可替代的典型用法
当你需要原表每一行都保留,并附加一个组内序号时,GROUP BY 根本做不到——它强制压缩行数,丢掉原始明细。
- 常见错误现象:
SELECT *, MAX(brdy) FROM t GROUP BY psn_no可能返回错的id或name(MySQL 5.7+ 严格模式会报错) - 使用场景:去重删冗余行、取每个分组 Top-N、计算移动平均、生成分页锚点
- 参数差异:
PARTITION BY决定“窗口边界”,ORDER BY决定编号顺序,缺一不可 - 实操建议:MySQL 8.0+ 才支持;旧版本必须用变量模拟,但并发不安全
WHERE 里不能直接用 ROW_NUMBER,这是硬限制
WHERE rn = 1 这种写法必然报错,因为窗口函数在 SQL 执行顺序中晚于 WHERE,此时 rn 还不存在。
- 常见错误现象:
ERROR 3593: Window function 'row_number' is not allowed in this context - 正确写法只能是子查询套一层:
SELECT * FROM (SELECT *, ROW_NUMBER() OVER (...) AS rn FROM t) t2 WHERE rn = 1 - 容易踩的坑:有人把
ORDER BY放在子查询外层,导致窗口内排序失效,rn分配错乱 - 性能注意:多一层嵌套不增加计算量,但别在子查询里做无谓
SELECT *,只选必要列减少内存开销
GROUP BY 和 ROW_NUMBER 混用时执行顺序很关键
窗口函数总在 GROUP BY 之后运行,所以你能对聚合结果再开窗,但不能反过来。
- 合法写法:
SELECT user_id, SUM(amount) AS total, ROW_NUMBER() OVER (ORDER BY SUM(amount) DESC) AS rank FROM orders GROUP BY user_id - 非法写法:
ROW_NUMBER() OVER (PARTITION BY user_id) ... GROUP BY user_id—— 这会报错,因为窗口函数不能在 GROUP BY 里引用未聚合列 - 兼容性提醒:SQLite 3.25+、PostgreSQL、SQL Server 全支持;MySQL 5.7 及以下直接报
ERROR 1064 - 容易忽略的点:
PARTITION BY列必须和GROUP BY列一致或为其超集,否则语义矛盾
真正卡住性能的往往不是函数本身,而是有没有索引支撑排序和分组,以及你到底要不要保留原始行——选错方向,优化索引也救不回来。

















