clickhouse 21.8+ 实验性支持部分窗口函数,但不支持标准 row_number() over (order by ...);其“伪流式”实现需全分组加载内存排序,无磁盘落刷机制,易oom;替代方案为group by + arraysort + arrayslice等数组操作。

ClickHouse 支持窗口函数吗?先确认执行环境
ClickHouse 21.8+ 才开始实验性支持部分窗口函数(如 rowNumberInAllBlocks、rank()、sum() OVER ()),但**不支持标准 SQL 的 ROW_NUMBER() OVER (ORDER BY ...) 全量排序类窗口函数**。很多用户报的“窗口函数 OOM”,其实是误用了语法,或在旧版本强行写 OVER 导致解析失败后退化为全表扫描 + 内存聚合。
验证方式很简单:执行 SELECT version(),若低于 22.3,基本可判定你写的 OVER 没生效;用 EXPLAIN SYNTAX 看是否被重写成子查询 + arrayEnumerate 类模拟逻辑——这种模拟本身就会放大内存压力。
为什么 sum(x) OVER (PARTITION BY a ORDER BY b) 会爆内存
即使新版支持,ClickHouse 的窗口函数实现是「伪流式」:它仍需将整个 PARTITION BY 分组的数据加载进内存,再按 ORDER BY 排序后逐行计算。这和 PostgreSQL/MySQL 的 WindowAgg 节点本质一样,但 ClickHouse 缺少磁盘落刷机制(max_bytes_before_external_window 这类参数根本不存在)。
- 分区数据量 > 几十万行 → 内存占用直线上升,
memory_usage在system.query_log里常显示 GB 级 -
ORDER BY字段无索引 → 强制全分组数据排序,无法跳过无关块 - 使用
ROWS BETWEEN UNBOUNDED PRECEDING AND CURRENT ROW→ 不缓解问题,ClickHouse 目前忽略该子句,仍做全分组累积
替代方案:用 GROUP BY + ARRAY JOIN 模拟窗口逻辑
对绝大多数业务场景(比如“每个用户最近 3 笔订单金额累计”),可用确定性聚合 + 数组展开替代,规避排序开窗:
SELECT
user_id,
order_time,
amount,
arraySum(arraySlice(amt_arr, 1, 3)) AS recent_3_sum
FROM (
SELECT
user_id,
groupArray((order_time, amount)) AS orders,
arrayMap(x -> x.2, arraySort(x -> -x.1, orders)) AS amt_arr
FROM orders
WHERE order_time >= '2026-09-01'
GROUP BY user_id
)
ARRAY JOIN order_time, amount
关键点:
- 先
GROUP BY user_id把数据分片,控制单次聚合内存上限 - 用
arraySort在数组内排序(比全局窗口排序轻量得多) -
arraySlice和arraySum实现滑动窗口语义,不依赖OVER - 务必加
WHERE过滤时间范围,避免groupArray吃光内存
真要开窗?必须限制输入规模 + 关闭并发
如果业务强依赖标准窗口语法(如对接 BI 工具),唯一可行路径是把数据量压到万级以内:
- 前置物化视图:用
MATERIALIZED VIEW预聚合高频分区,例如按天预计算user_id + day级别汇总,再在其上开窗 - 强制单线程:加
SETTINGS max_threads = 1,防止多线程把内存翻倍耗尽 - 禁用缓存:加
SETTINGS use_uncompressed_cache = 0,避免重复查询时缓存放大内存占用 - 绝不要在分布式表上直接开窗:
Distributed表会把窗口逻辑下推到每个 shard,各节点各自加载全量分区数据 → 内存 × shard 数
窗口函数在 ClickHouse 里不是“能用就行”的功能,而是个高风险操作。它的内存模型决定了:只要输入没被严格约束,OOM 就是确定性结果,不是概率事件。










