窗口函数应优先用于每行加组内统计值场景,因其仅扫描表一次且性能稳定;子查询在mysql中可能触发10万次dependent subquery,且null处理、排名、前后行取值等功能无法简洁高效实现。

窗口函数不是万能替代品,但绝大多数涉及“每行加一个组内统计值”的场景,应该优先用窗口函数而不是子查询。
查每个分组的聚合值(比如部门平均工资)
直接用 AVG(salary) OVER (PARTITION BY dept_id),别写 (SELECT AVG(salary) FROM t2 WHERE t2.dept_id = t1.dept_id)。
- 前者只扫描表一次,后者在 MySQL 中会显示为
DEPENDENT SUBQUERY,10 万行员工数据就执行 10 万次子查询 - 如果
dept_id有索引,子查询也能快一点,但优化器不一定重写成 JOIN;窗口函数则稳定走一次排序 + 一次遍历 - 注意 NULL:
PARTITION BY dept_id把所有NULL归为一组,而子查询里t2.dept_id = t1.dept_id对NULL返回UNKNOWN,结果不等价——要用COALESCE(dept_id, -1)对齐语义
需要排名、序号或前后行取值(比如最新订单、环比)
必须用窗口函数,子查询要么写不简洁,要么性能崩盘。
-
ROW_NUMBER() OVER (PARTITION BY user_id ORDER BY created_at DESC, id DESC)替代NOT EXISTS或LEFT JOIN找最新记录 -
LAG(amount, 1, 0) OVER (PARTITION BY user_id ORDER BY event_time)替代JOIN t2 ON t2.id = t1.id - 1——后者依赖 ID 连续,生产环境根本不可靠 - 没写二级排序(如
created_at DESC, id DESC)时,同时间戳下ROW_NUMBER()每次结果可能不同,且 MySQL 8.0 不保证物理顺序
想在 WHERE 条件里过滤排名或累计值
窗口函数不能直接出现在 WHERE,必须套一层子查询或 CTE。
- 错:
WHERE ROW_NUMBER() OVER (...) → 语法错误 - 对:
SELECT * FROM (SELECT *, ROW_NUMBER() OVER (ORDER BY score DESC) rn FROM t) t2 WHERE t2.rn - 别为了省一层嵌套硬改逻辑——比如用
HAVING或变量模拟,MySQL 用户变量在并发或复杂 JOIN 下行为不稳定
哪些情况还得用子查询
不是所有子查询都能/该被窗口函数替代。
- 单值存在性判断:如
WHERE EXISTS (SELECT 1 FROM config WHERE key = 'feature_x' AND value = 'on'),开销极小,没必要上窗口 - 跨多表、带
DISTINCT或GROUP BY的子查询,优化器常无法重写,但窗口函数也处理不了这种结构,此时 CTE 分步写更清晰 - MySQL 5.7 或旧版 PostgreSQL —— 窗口函数压根不支持,别强行套
OVER(),会报错ERROR 1064
真正容易被忽略的是排序字段的索引和 NULL 处理:哪怕你写对了 OVER (PARTITION BY a ORDER BY b),如果 b 没索引,数据库就得磁盘排序;如果 a 或 b 含大量 NULL,结果可能和预期不一致——这两点比选函数本身更容易拖垮性能。











