MySQL 8.0窗口函数非语法糖,可将嵌套查询从O(n²)优化至O(n log n);RANK()跳号、DENSE_RANK()不跳号、ROW_NUMBER()强制唯一,语义决定选型;必须用OVER()且ORDER BY不可省;WHERE不能直接过滤窗口结果,需CTE或子查询;无索引排序字段会导致性能断崖式下跌。

MySQL 8.0 的窗口函数不是语法糖,而是能直接把嵌套子查询从 O(n²) 降为 O(n log n) 的底层能力。只要版本 ≥ 8.0.2、sql_mode 没禁用窗口函数,就能用 ROW_NUMBER()、RANK()、DENSE_RANK() 一行替代多层关联。
为什么 RANK() 和 DENSE_RANK() 不能混用
业务语义决定函数选择,不是“哪个更顺手”:
-
RANK()遇到并列会跳号(如分数 90,90,85 → 排名 1,1,3),适合颁奖场景——两个亚军,就没有季军 -
DENSE_RANK()并列不跳号(同上 → 1,1,2),适合报表档位划分,比如“前3名”必须严格返回最多3人 -
ROW_NUMBER()强制唯一编号(→ 1,2,3),哪怕分数全相同,仅用于生成行号,不反映真实并列关系 - 三者都必须带
OVER(),且ORDER BY不可省;漏写PARTITION BY dept就会变成全表排名,不是按部门分组排名
WHERE 中无法直接过滤窗口函数结果
窗口函数在 SQL 执行顺序中晚于 WHERE,所以以下写法必然报错 Unknown column 'rk':
SELECT *, RANK() OVER (PARTITION BY dept ORDER BY salary DESC) AS rk FROM employees WHERE rk = 1;
正确做法只有两种:
- 用 CTE 包一层:
WITH ranked AS (SELECT *, RANK() OVER (...) AS rk FROM employees) SELECT * FROM ranked WHERE rk = 1 - 或子查询:
SELECT * FROM (SELECT *, RANK() OVER (...) AS rk FROM employees) t WHERE t.rk = 1 - 别试图在
WHERE里用别名,也别指望加索引能让它变合法——这是执行逻辑决定的,不是性能问题
ORDER BY 字段没索引时性能会断崖式下跌
窗口函数依赖排序,而 ORDER BY salary DESC 若字段无索引,MySQL 必走 filesort + 临时表,百万级数据可能从毫秒级拖到秒级:
- 用
EXPLAIN检查执行计划,重点关注Extra列是否含Using filesort或Using temporary - 对高频用于窗口排序的字段(如
sale_date、salary)务必建联合索引,例如INDEX(dept, salary)能同时支撑PARTITION BY dept ORDER BY salary DESC - NULL 值默认排最后(
NULLS LAST),但行为受 SQL 模式影响,线上环境建议显式写ORDER BY salary DESC NULLS LAST,避免跨版本不一致
最易被忽略的是:窗口函数的 ORDER BY 不只是语义需要,它直接绑定执行计划路径;没索引的排序字段会让所有优化归零,再简洁的 OVER() 也救不回性能。


















