窗口函数性能瓶颈源于全量排序与分区,需为OVER()中列建严格顺序复合索引,避免高基数分区、函数ORDER BY及嵌套计算,并确保字符集一致与数据流路径最优。

窗口函数本身不慢,慢是因为你让它在全量数据上做排序和分区——尤其当没索引、分区键基数高、或ORDER BY字段无法走索引时。
为什么EXPLAIN看到rows巨大,但实际只查10行?
MySQL执行窗口函数前,必须先把满足WHERE条件的所有行读出来,按OVER()里的PARTITION BY和ORDER BY完成全局排序或分区内排序,再计算窗口值。这个过程不走“跳过”逻辑,也不支持索引下推到窗口计算阶段。
-
ROW_NUMBER() OVER (ORDER BY created_at DESC)会强制对所有匹配行排序,哪怕你最后只取rn BETWEEN 1001 AND 1010 - 如果
created_at没索引,或索引因WHERE条件失效(比如WHERE status = 'done' AND UPPER(name) = 'ABC'),就会触发全表扫描+文件排序 -
EXPLAIN里rows显示的是排序前的预估行数,不是最终返回数——它已经告诉你:这一步要处理多少数据
ORDER BY字段没索引,窗口函数就几乎必然变慢
窗口函数的ORDER BY(以及PARTITION BY)是性能关键路径。MySQL不能跳过排序直接编号,而排序成本随数据量非线性增长。
- 必须为
OVER()中出现的列建复合索引,顺序严格对应:(partition_col, order_col)或(order_col)(无PARTITION BY时) - 避免在
ORDER BY里用函数,如ORDER BY DATE(created_at)——这会让索引失效;改用生成列+函数索引:created_date DATE AS (DATE(created_at)) STORED,再建索引 - 字符集不一致也会让索引“隐身”:检查
SHOW FULL COLUMNS FROM t1 LIKE 'status';里的Collation,确保生成列、函数索引、查询条件三者COLLATE完全一致
分区键基数太高,内存直接打爆
每个分区都要单独维护一个排序上下文。若PARTITION BY user_id(500万不同值),MySQL就得开500万个窗口帧,极易OOM或大量使用磁盘临时表。
- 优先用业务可聚合的低基数字段分区,例如
PARTITION BY DATE(created_at)比PARTITION BY user_id更安全 - 实在要用高基数字段,先用子查询/CTE过滤出目标子集,再在其上跑窗口函数:
WITH filtered AS (SELECT * FROM orders WHERE created_at >= '2026-06-01') SELECT ..., ROW_NUMBER() OVER (...) FROM filtered - 调大
sort_buffer_size(如4M)能缓解小分区排序压力,但治标不治本;tmp_table_size和max_heap_table_size也要同步检查
别在窗口函数里嵌套复杂表达式
每一层窗口计算都需重排数据。嵌套ROW_NUMBER() OVER (ORDER BY ...)再套SUM() OVER (PARTITION BY ...),等于做两次全量排序。
- 用CTE拆解:先算排名,再基于排名结果算累计值,中间结果可复用
- 避免
ROWS BETWEEN UNBOUNDED PRECEDING AND CURRENT ROW这种宽泛帧定义——如果只要最近7天累计,显式写ROWS BETWEEN 6 PRECEDING AND CURRENT ROW - 确认是否真需要窗口函数:纯分页场景,
LIMIT offset, size在小offset下仍比ROW_NUMBER()快;只有深度分页+需稳定结果时,才值得换
最常被忽略的一点:窗口函数优化不是加个索引就完事,而是要让整个数据流——从WHERE过滤、到索引定位、再到排序输入——全部落在最优路径上。任何一环脱节,性能就会断崖下跌。


















