Snowflake动态表增量刷新:复用已有聚合结果,性能成本怎么优化
对于动态表定义中位于顶层的受支持聚合操作,Snowflake 现在可以利用已经存储的聚合结果进行刷新,无需从头重新计算受影响的数据分组。
2026 年,智能体在企业级应用里会有哪些实质性突破?《2026 年 AI 与数据发展预测》白皮书现已开放下载,专家前瞻一手掌握,提前拥抱新的工作方式。
在 Snowflake 动态表(Dynamic Tables)的客户中,聚合流水线是最常见的模式之一:订单数据按客户汇总、会话数据按账户汇总,或者传感器读数按设备汇总。用户只要在动态表里定义GROUP BY,流水线自己就能让数据保持最新,刷新逻辑一行都不用写。
针对这种模式,Snowflake 已经把运行效率提上去了。动态表定义中处于顶层的受支持聚合操作,现在可以直接拿已经存下来的聚合结果来刷新,受影响的数据分组不必从头重算。
整个过程全自动:不用加新关键字,也不用配任何选项。只要动态表的定义符合相应结构要求,优化就会自己生效。下面说说它到底怎么运作。
增量刷新里有个问题容易被忽略
设想一张动态表,持续汇总每块电表的累计用电量和峰值读数:
CREATE OR ALTER DYNAMIC TABLE dt_meter_consumption TARGET_LAG = '5 minutes' WAREHOUSE = pipeline_whAS SELECT meter_id, SUM(kwh) AS kwh_total, MAX(kwh) AS kwh_peak FROM meter_readings GROUP BY meter_id;按说这条查询应该能增量更新,成本也该划算。毕竟meter_readings里变动的数据只占一小部分,计算量理应不大。
这个判断对了一半。假定新进来 100 条读数,涉及 40 个电表,那受影响的就只有 40 个数据分组,剩下数百万个分组没动,完全可以跳过。把计算范围收窄到受影响的分组,是最容易做的优化,动态表一直具备这个能力。
难的是另一半:知道哪些分组变了,不等于就能直接算出它们更新后的值。聚合值得根据分组内所有行算出来,所以最直接的办法,就是把该分组的所有数据行重新读一遍再算一次。结果就是,某块电表哪怕只多了一条读数,只要它历史上已有 100 万条读数,这 100 万条就得重新读进来参与计算。变动的只有一行,成本却按整个分组来算。
动态表此前的不对称性正在这里:计算量看的是受影响分组的大小,而不是引发变化的数据量。

用已有结果做增量计算
优化的关键在于,动态表本身就存着上一次刷新的计算结果。如果某块电表此前的累计用电量是已知的,之后又多了一条读数,那算新的累计值时,多年的历史数据根本不用再翻一遍。
Snowflake 现在走的就是这条路。对符合条件的动态表定义,刷新时会复用动态表里已存储的值,只根据真正发生变化的行来更新聚合结果。计算量于是和变化的数据量成正比,不再由受影响分组的大小决定。
具体怎么实现,要看聚合函数是哪个。SUM是最简单的情形,新值可以直接在旧值上做算术:
newSum = oldSum + insertedSum - deletedSum换成直白的话:新值等于原来存的值,加上新增数据的汇总值,再减掉删除数据的汇总值,源数据行一条都不用读。计数也是同样的路子,Snowflake 基于求和与计数推导出的其他聚合结果同样沾光,平均值、标准差、方差都算在内。
对于 MAX,只要数据变化只涉及新增行,也能用类似办法:新读数要么超过此前存的峰值,要么没超过。所以新值就是max(oldPeak, insertedPeak)。
删除操作就更麻烦一些,因为不是所有聚合函数都可逆。对于SUM,照着公式做减法就行:如果从meter_readings从累计值里删掉一条重复的 12 kWh 读数,总数自然就少 12 kWh。可换成MAX就不一样了:被删掉的如果是当前最大值,原先存下的那个最大值已经作废,新的最大值可能藏在同组其余 100 万条数据里的任何位置,光靠旧值算不出来。Snowflake 能认出这类情况并重算聚合值,不过只对确实需要重算的分组动手,其余分组照样吃到新优化的红利。

能提速多少,要看聚合函数和数据怎么变,但整体幅度相当可观。Snowflake 的内部测量只统计符合此次优化条件、带顶层GROUP BY操作的动态表,且范围限于此前刷新超过 3 秒的情况。结果是总刷新耗时平均下降约 40%,超过三分之二的刷新任务耗时至少少了 10%。单次最大提升出现在一次原本约需 377 秒的刷新上,优化后约 2.5 秒就跑完了,提升 147 倍。

聚合之上再叠连接操作时,
这套优化对结构卡得很死:GROUP BY聚合必须是动态表的顶层操作。一旦聚合上面还压着别的操作,比如连接(JOIN)或联合(UNION),优化就开不了。HAVING子句则没问题,它本来就属于分组操作的一部分。
举個例子:一张表记录每个电表的用电读数,再通过关联电表所属站点来补充信息:
CREATE OR ALTER DYNAMIC TABLE dt_site_consumption TARGET_LAG = '5 minutes' WAREHOUSE = pipeline_whAS SELECT s.site_name, s.tariff_band, m.kwh_total, m.kwh_peak FROM ( SELECT meter_id, SUM(kwh) AS kwh_total, MAX(kwh) AS kwh_peak FROM meter_readings GROUP BY meter_id ) m JOIN dim_sites s ON m.meter_id = s.meter_id;这个定义里,聚合藏在子查询内部,上面还套着连接,所以GROUP BY并非顶层操作,定义不满足优化条件。
办法是把两件事拆进两张动态表:第一张只管聚合,第二张拿聚合结果去做连接:
CREATE OR ALTER DYNAMIC TABLE dt_meter_consumption TARGET_LAG = DOWNSTREAM WAREHOUSE = pipeline_whAS SELECT meter_id, SUM(kwh) AS kwh_total, MAX(kwh) AS kwh_peak FROM meter_readings GROUP BY meter_id;CREATE OR ALTER DYNAMIC TABLE dt_site_consumption TARGET_LAG = '5 minutes' WAREHOUSE = pipeline_whAS SELECT s.site_name, s.tariff_band, m.kwh_total, m.kwh_peak FROM dt_meter_consumption m JOIN dim_sites s ON m.meter_id = s.meter_id;这样一来,聚合有了自己独立的动态表,GROUP BY只要动态表位于顶层、分组键和两个聚合值又都直接出现在查询的选择列表里,它就满足这次优化的条件。满足条件后,这张表能依据下游使用方的需求,按对应的调度计划刷新;连接操作则被挪到另一张专门做连接的动态表上。TARGET_LAG = DOWNSTREAM
拆分还带来一个额外好处:聚合结果如今可供其他需要它的对象复用。
怎样才能拿到这项优化带来的收益?
对于带顶层聚合操作的动态表,这次优化让它的性能和成本效益都往上走了一步。可以去查动态表刷新优化文档,看看自己用的动态表能不能从中受益。