keep first 是聚合函数修饰语,row_number() 是窗口函数;前者支持排序裁剪与早聚合,后者需全量排序编号再过滤,性能差异源于语义与执行路径不同。

KEEP FIRST 和 ROW_NUMBER() 的执行路径根本不同
KEEP FIRST 不是窗口函数,而是聚合函数的修饰语;ROW_NUMBER() 是标准窗口函数。两者在 Oracle 执行引擎里走的是两条路:KEEP 被优化器识别为“分组内排序后裁剪再聚合”,而 ROW_NUMBER() 必须维护完整窗口帧、逐行编号并保留所有中间行——哪怕你最后只取 rn = 1,Oracle 仍得把整组都排完、编完号,才能在外层过滤。
为什么 KEEP FIRST 常比子查询套 ROW_NUMBER() 快
常见写法差异直接拉大开销:
-
MAX(name) KEEP (DENSE_RANK FIRST ORDER BY hire_date DESC):优化器有机会做 early-aggregation,排序时若发现已找到首个非空候选,可能提前终止部分比较(尤其当hire_date有索引且数据分布倾斜) -
SELECT name FROM (SELECT name, ROW_NUMBER() OVER (PARTITION BY dept_id ORDER BY hire_date DESC) rn FROM emp) WHERE rn = 1:必须完成全部排序 + 全量编号 + 全量物化子结果集,再过滤——多一次 Sort + 多一次 Table Access - 当
ORDER BY字段存在大量重复值(比如上千人同一天入职),ROW_NUMBER()仍要为每一行分配唯一序号;而DENSE_RANK FIRST只需确认哪些行属于“首组”,再对它们聚合,逻辑上更轻
容易踩的坑:你以为在比函数,其实是在比写法和语义
性能差异往往不是函数本身导致的,而是你没对齐语义就硬换写法:
- 用
ROW_NUMBER()+WHERE rn = 1只能返回一行;但MAX(name) KEEP (DENSE_RANK FIRST ...)在并列时会返回所有首组行中name的最大值——它本质是聚合,不是取行 - 想严格等价于
ROW_NUMBER() = 1,得加MIN(id)或MAX(id)配合KEEP,否则业务逻辑已偏移 -
KEEP中的ORDER BY不能写表达式(如TO_DATE(date_str, 'YYYYMMDD')),否则报ORA-01747;而ROW_NUMBER()支持——这常逼你多套一层子查询,反而抵消 KEEP 的优势
真正影响性能的其实是 PARTITION BY 列的数据倾斜
无论用 KEEP 还是 ROW_NUMBER(),只要 PARTITION BY dept_id 中某个部门占全表 80% 数据,排序压力就集中在那一组。此时:
- 索引是否覆盖
dept_id + hire_date才是关键,函数选型只是表象 -
KEEP更容易触发索引快速全扫描(INDEX FAST FULL SCAN)+ 排序裁剪,但前提是统计信息准确、CBO 没被误导 - 如果
hire_date列 NULL 值极多,DENSE_RANK默认把 NULL 排最前(除非显式写NULLS LAST),可能导致首组全是 NULL 行,聚合结果为空——这种逻辑偏差比性能问题更难排查











