窗口函数性能瓶颈主因是order by字段无索引导致分区内部排序开销大,需建复合索引、避免表达式、降低分区基数、限定窗口帧、提前过滤及合理使用物化或覆盖索引。

窗口函数里ORDER BY没走索引,查得慢
根本问题不是ROW_NUMBER()或SUM() OVER本身慢,而是每个分区内部必须按ORDER BY字段排序——如果该字段没索引,数据库就得对每个分区做一次完整排序,数据量大时直接触发磁盘External Sort。
- 用
EXPLAIN看执行计划,重点找Sort节点的Actual Total Time和是否出现disk字样 - 复合索引必须严格匹配
PARTITION BY列 +ORDER BY列顺序,例如PARTITION BY region ORDER BY created_at,对应索引应为(region, created_at) - 避免在
ORDER BY里用表达式,比如ORDER BY DATE(created_at)——普通索引失效,除非建函数索引(PostgreSQL/MySQL 8.0+支持) - 如果只用
ROW_NUMBER() OVER (PARTITION BY x)且不关心顺序,干脆删掉ORDER BY,数据库可能跳过排序
分区键基数太高,内存爆了
按user_id分区查每个用户最新订单,用户数超千万时,窗口函数会为每个user_id维护独立排序上下文,CPU和内存压力陡增,甚至OOM。
- 优先选业务上自然聚合的低基数字段,如
region、product_category,而非order_id或毫秒级时间戳 - 高基数时间字段可降维:把
created_at转成DATE(created_at)再分区,分区数从百万级降到百级 - 用
EXPLAIN观察WindowAgg节点的Partitions数,超过10万必须重构分区逻辑 - 必要时改用
GROUP BY+ 聚合函数替代,比如MAX(created_at)比ROW_NUMBER() OVER (PARTITION BY user_id ORDER BY created_at DESC)更轻量
窗口帧太宽,扫描行数爆炸
默认RANGE UNBOUNDED PRECEDING在累计求和场景中,每行都要扫描整个分区,性能随分区大小线性恶化。10万行分区下,单次查询可能扫描10亿行。
- 显式限定帧范围:
ROWS BETWEEN 10 PRECEDING AND CURRENT ROW比默认帧快一个数量级 - 用
ROWS代替RANGE(尤其当排序字段有重复值时),ROWS基于物理行偏移,RANGE需额外去重和范围匹配 - 提前用
WHERE过滤数据,别让窗口函数处理全量表——比如先筛出status = 'paid'再开窗 - 高频稳定查询考虑物化:把开窗结果存到临时表或物化视图,避免重复计算
ORDER BY + LIMIT组合拖垮查询
像SELECT * FROM t ORDER BY ts DESC LIMIT 10这种语句,表面只取10行,但数据库仍要对全部匹配行排序,300万行时耗时可能达30秒。
- 创建覆盖索引,包含所有
WHERE过滤字段 +ORDER BY字段 +SELECT字段,例如(status, ts, id, name) - 避免
OFFSET分页,改用游标分页:WHERE ts - OR条件会破坏索引,改用
IN:status IN (0, -1)比status = 0 OR status = -1更容易走索引 - 如果只需首行,且排序字段有索引,确认该索引是聚簇索引(如MySQL主键)或包含必要列,否则仍要回表+排序
真正卡住性能的,往往不是语法写错,而是分区键选得太细、排序字段没索引、或者默认窗口帧没收缩——这些细节在EXPLAIN里都藏得住,但一查就露馅。











