keep的真实执行路径是:先按partition by分组,组内对order by字段全量dense_rank排序,再筛选rank=1的行,最后在这些行中执行聚合函数;其性能取决于索引与数据分布,而非语法简洁性。

KEEP 语法本身不是性能优化手段,而是语义更紧凑的写法;它在金仓或 Oracle 中执行效率取决于底层执行计划是否能下推排序+裁剪,而不是语法表面看起来“更少计算”。
KEEP 语法实际执行逻辑是什么
KEEP 并不跳过排序,也不跳过聚合扫描。比如 MIN(mc) KEEP (DENSE_RANK FIRST ORDER BY sl) 的真实执行路径是:
- 先按
partition by id分组 - 组内对
sl做DENSE_RANK排序(注意:不是只排前几行,而是全量排序生成稠密排名) - 找出所有
DENSE_RANK = 1的行(可能多行,因sl相同) - 在这些行中再对
mc执行MIN()
所以它和手写 ROW_NUMBER() OVER (...) = 1 + 子查询相比,逻辑等价但解析阶段更轻量——省去显式子查询嵌套,但执行开销几乎一致。
为什么有时 KEEP 比 FIRST_VALUE/LAST_VALUE 快
关键不在 KEEP,而在 Oracle/金仓对 KEEP 的执行器优化更成熟。常见对比场景:
-
FIRST_VALUE(mc) OVER (PARTITION BY id ORDER BY sl):必须维护窗口帧、逐行输出,且默认RANGE BETWEEN UNBOUNDED PRECEDING AND CURRENT ROW,可能触发额外排序或缓冲 -
MIN(mc) KEEP (DENSE_RANK FIRST ORDER BY sl) OVER (PARTITION BY id):优化器更容易识别“只需首组极值”,有机会做 early-aggregation 或 rank 截断
尤其当 sl 有大量重复值时,DENSE_RANK FIRST 可能命中多行,而 FIRST_VALUE 只返回第一行——二者语义不同,不能直接比性能,得先确认业务要的是“首组所有行中的 MIN”还是“排序后第一行的值”。
哪些情况 KEEP 反而拖慢查询
KEEP 不是银弹,以下场景容易翻车:
- 在没有
id和sl联合索引的情况下直接跑OVER (PARTITION BY id ORDER BY sl),会强制 Sort Area 内存排序,OOM 风险高 - 误用聚合函数搭配:比如写
COUNT(*) KEEP (DENSE_RANK FIRST ORDER BY sl)——COUNT不接受 KEEP 修饰,Oracle 报错ORA-30496: Argument should be a constant or a function of expressions in the GROUP BY clause - 在金仓数据库中混用大小写:虽然支持 KEEP,但某些版本对
DENSE_RANK中间空格敏感,写成DENSE_RANKFIRST(无空格)会解析失败 - 想用
AVG或SUM配合KEEP却忽略分母问题:例如SUM(sal) KEEP (DENSE_RANK LAST ORDER BY hiredate)是合法的,但它只对最后一批入职员工求和,不是“最后一个人的 sal”,这点极易误解
真正影响 KEEP 性能的关键点
别盯着语法改写,重点看三处:
- 确保
PARTITION BY字段上有索引,且该字段选择性足够(避免单个分组过大) -
ORDER BY字段最好有索引前缀,或与分区字段组成联合索引,例如(id, sl) - 如果只是取“每组排序后第一行的某个字段”,用
FIRST_VALUE更直白;如果要“首组多行中该字段的 MIN/MAX”,才非用 KEEP 不可——强行统一用 KEEP 反而增加理解成本和调优难度
复杂点在于:KEEP 的语义是“先分组、再稠密排名、再从首/尾组里聚合”,这个链条中任意一环数据倾斜(比如某个 id 占全表 80% 行数),都会让整个窗口操作退化为单点瓶颈。没索引支撑时,它不会变快,只会报错更晚、排查更难。











