窗口函数性能取决于OVER子句的正确写法与索引匹配:必须含ORDER BY,字段需有索引;漏PARTITION BY会逻辑错误;RANK()适合并列跳号场景,DENSE_RANK()适合档位划分,ROW_NUMBER()用于唯一编号;SUM() OVER应优先用ROWS避免重复值干扰;WHERE不能引用窗口函数结果,须用CTE或子查询。

ROW_NUMBER()、RANK()、SUM() OVER 这类窗口函数本身不“设置”,而是必须严格写对 OVER 子句 + 配套索引,否则不仅没提速,反而比子查询更慢。
为什么直接写 ROW_NUMBER() OVER () 会出问题
MySQL 8.0 解析器强制要求 OVER 中至少包含 ORDER BY(PARTITION BY 可选但漏写就全表计算)。只写 ROW_NUMBER() OVER () 会报错 ERROR 1064;而写成 ROW_NUMBER() OVER (ORDER BY id) 却没加索引,会导致每次执行都触发 Using filesort 和 Using temporary,百万行数据从毫秒级拖到秒级。
-
OVER缺ORDER BY→ 语法拒绝执行 -
ORDER BY字段无索引 → 执行计划崩坏,性能断崖下跌 - 漏写
PARTITION BY dept_id→ 本想算“部门内 Top 3”,结果变成“全公司 Top 3”
RANK() 和 DENSE_RANK() 怎么选才不翻车
不是看哪个名字顺口,是看业务是否允许并列跳号。比如发奖金:两个员工同分 95 分,RANK() 给出 1,1,3,意味着第三名实际是第四人;DENSE_RANK() 是 1,1,2,第三名就是真正第三人。选错一个,报表逻辑就错一片。
- 颁奖/榜单场景 → 用
RANK()(并列跳号) - 档位划分/取前 N 名(如“前3名员工”必须返回≤3人)→ 用
DENSE_RANK() - 纯编号/分页游标 → 用
ROW_NUMBER()(强制唯一,哪怕值全相同) - 三者都必须带
OVER (PARTITION BY ... ORDER BY ...),不能省
SUM() OVER 累计求和必须用 ROWS,别碰 RANGE
RANGE BETWEEN UNBOUNDED PRECEDING AND CURRENT ROW 看起来简洁,但只要 ORDER BY 字段有重复值(比如多笔订单同一天),它就把所有同值行全塞进当前窗口,导致累计值虚高、不可控。而 ROWS 按物理行序滑动,稳定可预期。
- 安全写法:
SUM(sales) OVER (ORDER BY sale_date, id ROWS UNBOUNDED PRECEDING)(补id防重复) - 日期有缺失?
ROWS按行数不是按日历天数 → 要自然日滚动,得先用GENERATE_SERIES(MySQL 8.0.29+)补日期再LEFT JOIN -
RANGE在 MySQL 中不支持INTERVAL表达式(如RANGE BETWEEN INTERVAL 7 DAY PRECEDING),那是 PostgreSQL 的写法,直接报错
WHERE 里不能用窗口函数结果,这是执行顺序决定的
WHERE 在 SQL 执行顺序中早于窗口函数计算,所以 SELECT *, RANK() OVER (...) AS rk FROM t WHERE rk = 1 必然报错 Unknown column 'rk'。这不是语法错误,是执行阶段根本还没算出 rk。
- 正确做法只有两种:
WITH ranked AS (SELECT *, RANK() OVER (...) AS rk FROM t) SELECT * FROM ranked WHERE rk = 1 - 或子查询:
SELECT * FROM (SELECT *, RANK() OVER (...) AS rk FROM t) t2 WHERE t2.rk = 1 - 别指望加索引能绕过这个限制——这是执行模型决定的,不是性能问题
真正卡住效率的从来不是函数本身,而是 OVER 里那几个字段有没有联合索引,以及你有没有意识到 ORDER BY 不只是排序,它直接绑定执行路径。没索引的 ORDER BY,再短的 OVER 也救不回性能。


















