优化增量刷新查询¶
当您设计新的动态表查询或想要优化现有动态表查询以进行增量刷新时,请使用此页面。本指南展示了哪些运算符表现良好,哪些运算符需要仔细处理,以及如何重组查询以获得更好的性能。
有关增量刷新 支持 哪些查询结构的完整列表,请参阅 支持的动态表查询。
按运算符划分的性能预期¶
在优化动态表查询之前,请了解哪些运算符可以从增量刷新中受益,哪些运算符可能会导致问题。
备注
由于查询优化和仓库调度等固定开销,短查询(少于 10 秒)的性能提升可能较小。
始终表现良好的运算符¶
这些运算符可通过增量刷新高效工作:
SELECT
WHERE
FROM <base table>
UNION ALL
QUALIFY [ RANK | ROW_NUMBER | DENSE_RANK ] ... = 1
有关 Snowflake 如何处理每个运算符的详细信息,请参阅 运算符引用表。
受数据局部性影响的运算符¶
对于这些运算符,性能取决于 数据局部性,这就是您组织数据的方式,以及发生与键相关的变更的位置:
INNER JOIN
OUTER JOIN
GROUP BY
DISTINCT
OVER(窗口函数)
当变更仅影响一小部分分组或分区键时,这些运算符表现良好。较差的数据局部性或分布在多个键上的变更可能会使增量刷新比完全刷新 更慢。
有关 Snowflake 如何处理每个运算符的详细信息,请参阅 运算符引用表。
常见优化模式¶
以下部分展示了优化使用局部性敏感运算符的查询的常见模式。
优化聚合¶
当您使用 GROUP BY 时,Snowflake 会为包含变更的每个分组键重新计算汇总。性能取决于以下因素:
数据群集:通过分组键聚类的源数据性能最佳。
变更分布:旨在影响不到 5% 的分组键的变更。
键复杂性:简单列引用的性能优于复合表达式。
问题:分组键中的复合表达式
此查询性能不佳,因为分组键是一个表达式:
CREATE DYNAMIC TABLE hourly_sums
TARGET_LAG = '1 hour'
WAREHOUSE = my_warehouse
AS
SELECT DATE_TRUNC('minute', ts), SUM(amount)
FROM transactions
GROUP BY 1;
解决方案:物化表达式
拆分为两个动态表以公开一个简单的分组键:
CREATE DYNAMIC TABLE transactions_with_minute
TARGET_LAG = DOWNSTREAM
WAREHOUSE = my_warehouse
AS
SELECT DATE_TRUNC('minute', ts) AS ts_minute, amount
FROM transactions;
CREATE DYNAMIC TABLE hourly_sums
TARGET_LAG = '1 hour'
WAREHOUSE = my_warehouse
AS
SELECT ts_minute, SUM(amount)
FROM transactions_with_minute
GROUP BY 1;
现在,GROUP BY 操作的是一个简单的列,而中间表则具有更好的 数据局部性 优势。
优化联接¶
联接性能取决于哪一方发生变化以及数据聚类的方式。
INNER JOIN:Snowflake 将左侧的变更与右表联接,然后将右侧的变更与左表联接。当一侧较小或不经常变更时,联接性能良好。
OUTER JOIN:Snowflake 还必须计算不匹配行的 NULL 值。哪一侧的变更会显著影响性能。
问题:两侧的大表,群集效果较差
两个源表都不按联接键聚类:
CREATE DYNAMIC TABLE order_details
TARGET_LAG = '1 hour'
WAREHOUSE = my_warehouse
AS
SELECT o.order_id, o.customer_id, p.product_name, o.quantity
FROM orders o
JOIN products p ON o.product_id = p.product_id;
解决方案:对变更频率较低的表进行聚类
按联接键对维度表进行聚类。然后,联接受益于更好的局部性:
ALTER TABLE products CLUSTER BY (product_id);
CREATE DYNAMIC TABLE order_details
TARGET_LAG = '1 hour'
WAREHOUSE = my_warehouse
AS
SELECT o.order_id, o.customer_id, p.product_name, o.quantity
FROM orders o
JOIN products p ON o.product_id = p.product_id;
对于 OUTERJOINs:
将变更频率较高的表放在 LEFT 侧。
尽量减少 OUTER 关键字另一侧的变更。
对于 FULL OUTER JOINs,良好的局部性对双方都至关重要。
优化窗口函数¶
对于包含变更的每个分区键,Snowflake 重新计算 窗口函数。按照类似 GROUP BY 的方式对其进行优化。
关键要求:
始终包含 PARTITION BY 子句。不带 PARTITION BY 的窗口函数导致完全重新计算。
按分区键对源数据进行聚类。
将变更保留到少于 5% 的分区。
问题:无分区聚类的窗口函数
源表未按分区键聚类:
CREATE DYNAMIC TABLE ranked_sales
TARGET_LAG = '1 hour'
WAREHOUSE = my_warehouse
AS
SELECT
region,
salesperson,
amount,
RANK() OVER (PARTITION BY region ORDER BY amount DESC) as sales_rank
FROM daily_sales;
解决方案:按分区键聚类
按分区键对源表进行聚类,以便窗口函数受益于局部性:
ALTER TABLE daily_sales CLUSTER BY (region);
CREATE DYNAMIC TABLE ranked_sales
TARGET_LAG = '1 hour'
WAREHOUSE = my_warehouse
AS
SELECT
region,
salesperson,
amount,
RANK() OVER (PARTITION BY region ORDER BY amount DESC) as sales_rank
FROM daily_sales;
有效移除重复项(DISTINCT 与 QUALIFY)¶
DISTINCT 和 QUALIFY 可以移除重复项,但执行方式不同。
DISTINCT:等同于 GROUP BY ALL。局部性直接影响性能;局部性较差会导致刷新缓慢。
QUALIFY with ROW_NUMBER = 1:当动态表的顶级投影中存在模式 QUALIFY ROW_NUMBER() ... = 1 时,Snowflake 会对其进行优化。这种方法的执行速度始终快于完全刷新。
当 OVER() 子句中的所有 PARTITION BY 和 ORDER BY 键都保留在动态表中(包含在顶级投影中)时,优化效果最佳。
建议:如有可能,请使用 QUALIFY,而不是 DISTINCT
使用 DISTINCT:在导航菜单中,选择 ,然后选择您的动态表。
CREATE DYNAMIC TABLE unique_customers
TARGET_LAG = '1 hour'
WAREHOUSE = my_warehouse
AS
SELECT DISTINCT customer_id, customer_name, email
FROM customer_events;
使用 QUALIFY:在导航菜单中,选择 ,然后选择您的动态表。
CREATE DYNAMIC TABLE unique_customers
TARGET_LAG = '1 hour'
WAREHOUSE = my_warehouse
AS
SELECT customer_id, customer_name, email, event_time
FROM customer_events
QUALIFY ROW_NUMBER() OVER (PARTITION BY customer_id ORDER BY event_time DESC) = 1;
QUALIFY 版本更明确地表明要保留哪个副本(最新的副本),并且性能始终良好。
移除冗余 DISTINCT 操作
每个 DISTINCT 每次刷新都会消耗资源。如果您的数据已经是唯一的,或者您消除了上游的重复项,请移除不必要的 DISTINCT 子句。
运算符参考¶
下表解释了 Snowflake 在增量刷新期间如何处理每个 SQL 运算符。
运算符 |
Snowflake 处理方式 |
性能说明 |
|---|---|---|
SELECT |
仅将表达式应用于变更的行。 |
表现良好。没有特殊注意事项。 |
WHERE |
仅对变更的行计算谓词。 |
表现良好。成本随变化线性增长。注意:即便输出结果未发生变化,高选择性的 WHERE 子句仍可能要求仓库保持运行状态。 |
FROM <table> |
扫描自上次刷新以来 Snowflake 添加或移除的微分区。 |
成本与变更的分区数量成比例。将源表的变更限制在 5% 左右。 |
UNION ALL |
取双方变更的并集。 |
表现良好。没有特殊注意事项。 |
WITH (CTEs) |
计算每个公用表表达式的变更。 |
性能良好,但避免过于复杂的单表定义。考虑拆分为多个动态表。 |
标量汇总 |
当输入变更时,完全重新计算汇总。 |
避免在性能关键表中使用。考虑改为按常量分组。 |
GROUP BY |
重新计算变更的分组键的聚合。 |
通过对键进行分组来聚类源。避免在键中使用复合表达式。请参阅 优化聚合。 |
DISTINCT |
等同于 GROUP BY ALL。 |
局部性敏感。考虑改用 QUALIFY。请参阅 有效移除重复项(DISTINCT 与 QUALIFY)。 |
窗口函数 |
重新计算变更的分区键。 |
始终包含 PARTITION BY。按分区键聚类源。请参阅 优化窗口函数。 |
INNER JOIN |
将每一侧的变更与另一个表联接。 |
当一侧较小时,表现良好。将变化不频繁的一侧聚类。请参阅 优化联接。 |
OUTER JOIN |
将内部联接与 NOT EXISTS 查询相结合,以进行 NULL 计算。 |
对局部性最敏感的运算符。请参阅 优化联接。 |
LATERAL FLATTEN |
仅将展平应用于变更的行。 |
表现良好。成本随变化线性增长。 |
带排名的 QUALIFY |
使用 ROW_NUMBER/RANK/DENSE_RANK ... = 1 的优化路径。 |
高效率。将 QUALIFY 放置在动态表的顶级投影处。 |