窗口函数必须全量排序,执行阶段独立于WHERE和JOIN,需先生成完整结果集再按PARTITION BY和ORDER BY分区排序;ORDER BY字段须建联合索引(如(user_id, created_at DESC)),表达式排序(如UPPER(name))使索引失效;多窗口函数按书写顺序触发重排,ROW_NUMBER() OVER()不保证稳定顺序;rn=1过滤不减少排序量,应优先用GROUP BY+MAX或控制分区基数。

窗口函数执行阶段决定必须全量排序
窗口函数不是在WHERE或JOIN之后“顺便算一下”,而是被SQL标准规定为独立执行阶段:它必须等中间结果集完全生成后,再按PARTITION BY和ORDER BY做分区内部排序。哪怕你只取ROW_NUMBER() = 1,数据库也得把整个分区所有行读进内存、排好序,才能知道谁是第1行。
这不是优化器偷懒,是执行模型本身如此。你在EXPLAIN (ANALYZE)里看到WindowAgg节点挂在最外层,下面连着Seq Scan或没走索引的Index Scan,基本就等于确认:数据没提前过滤干净,排序字段没索引,或者分区太大。
ORDER BY字段没索引,性能直接崩
ROW_NUMBER() OVER (ORDER BY created_at DESC)不会复用created_at单列索引——它需要的是能支撑“先分组、再排序”的联合索引。比如PARTITION BY user_id ORDER BY created_at DESC,对应索引必须是(user_id, created_at DESC),顺序不能错,DESC也不能省。
- 仅对
created_at建索引,数据库仍要回表取出全部匹配行再排序 -
ORDER BY UPPER(name)这类表达式会让索引完全失效,必然触发Using filesort - MySQL 8.0基本不跳过排序;PostgreSQL 14+才可能识别极严格的索引复用条件
多个窗口函数并列时,排序顺序会互相影响
SQL引擎对并列的窗口函数不是“并行计算”,而是按书写顺序逐个处理。写在前面的ROW_NUMBER() OVER ()会先触发一次无序编号(实际按物理存储顺序),但一旦后面跟了ROW_NUMBER() OVER (ORDER BY xing),整个结果集就会被重排成xing顺序——前面那个r1也就跟着变“乱”了。
想保留原始顺序?别指望ROW_NUMBER() OVER (),它不保证稳定。真正可靠的做法是:加一个带主键或时间戳的显式排序字段,再在外层ORDER BY还原。
真正卡住性能的,常是误以为“只取一行就快”
常见错误是写SELECT * FROM (SELECT *, ROW_NUMBER() OVER (PARTITION BY uid ORDER BY ts DESC) rn FROM t) t2 WHERE rn = 1,以为只返回1行就省事。实际上:WHERE先过滤,然后全分区排序,最后才筛rn = 1——中间可能已对百万行完成排序。
更稳的替代方案:
- 业务允许时,改用
GROUP BY uid+MAX(ts)+ 关联原表取最新行 - 必须用窗口函数时,确保
PARTITION BY字段基数可控(避免百万级唯一值导致百万个小排序) - 超大分区累计计算(如用户全生命周期流水求
SUM() OVER ())没法靠索引解决,得接受近似、拆时间粒度或建物化中间表
窗口函数的排序开销藏得深,不看执行计划、不建对索引、不提前过滤,很容易在分页、去重、最新记录提取这类高频场景里突然变慢。

















