窗口函数执行早于DISTINCT,导致全量计算后去重、数据量爆炸;应分层处理:先取唯一分组键再JOIN计算;COUNT(DISTINCT) OVER()合法但易引发内存膨胀。

窗口函数在逻辑执行顺序中早于DISTINCT
DISTINCT 不是对“某几列”去重,而是对 SELECT 投影出的整行结果做去重。而 SQL 的逻辑执行顺序决定了:窗口函数(如 ROW_NUMBER()、COUNT(*) OVER ())在 SELECT 阶段就已计算完成,而 DISTINCT 是紧接在 SELECT 之后才执行的第 6 步。这意味着数据库必须先为**每一行原始数据**算出完整的窗口值,生成一个远大于实际需要的结果集,再在这个庞然大物上做去重——中间数据量爆炸是必然的。
常见错误写法直接触发全量计算+冗余排序
比如这个典型低效语句:SELECT DISTINCT user_id, COUNT(*) OVER (PARTITION BY user_id) FROM orders。它看似只想要每个 user_id 一行,但实际执行时:
- 数据库先扫描全表,对每行都计算一次
COUNT(*) OVER (PARTITION BY user_id),哪怕同一user_id出现 10 万次,就重复算 10 万次 - 接着把所有行(含重复
user_id+ 重复计数值)组成临时结果集,再按两列联合去重 - 整个过程无法利用
user_id索引跳过重复计算,也无法提前剪枝
替代方案必须分层剥离计算与去重
真正高效的做法,是把“哪些组要参与计算”和“怎么算”拆开:
- 用子查询或 CTE 先提取唯一分组键,例如:
SELECT DISTINCT user_id FROM orders - 再把这个小结果集 JOIN 回原表或聚合表,仅对必要数据做窗口/聚合
- 如果目标是“每个用户一条最新订单”,优先用
ROW_NUMBER() OVER (PARTITION BY user_id ORDER BY created_at DESC)+ 外层WHERE rn = 1,而不是靠DISTINCT硬压 - 若需统计去重后比例(如活跃用户占比),把窗口逻辑放在内层,
COUNT(DISTINCT)放在外层聚合,避免窗口内做去重
COUNT(DISTINCT) OVER() 是合法但危险的例外
COUNT(DISTINCT col) OVER (PARTITION BY x) 语法本身合法(PostgreSQL / MySQL 8.0+ / SQL Server 均支持),但它不是“先 DISTINCT 再开窗”,而是在每个窗口内独立做去重计数。问题在于:当窗口内 col 值高度离散(比如每行都不同),内存哈希表会急剧膨胀,极易触发磁盘临时表。这不是写法错,而是算法瓶颈——这种场景下,预聚合(GROUP BY x, col 后再汇总)往往更稳。

















