4. 查询性能分析工具
当一条查询在 Snowflake 上运行缓慢时,最忌讳的做法是凭直觉改 SQL 或盲目加大 Warehouse。正确的路径是:先用工具把“慢在哪里”量化出来,再决定是改 SQL、调 Warehouse、改数据布局,还是调整并发策略。本节围绕四类核心工具展开——Query Profile(图形化算子级剖析)、Query History(历史执行记录)、ACCOUNT_USAGE 视图(可聚合的元数据仓库)、EXPLAIN(执行前计划),并给出把它们串起来的瓶颈定位方法。
一、四类工具的分工与数据来源
这四类工具并非互相替代,而是覆盖“执行前—执行中—执行后—长期趋势”四个时间维度。理解它们各自的数据来源和粒度,是选对工具的前提。
二、Query Profile:算子级剖析的核心读法
Query Profile 是单条查询最细粒度的诊断视图,它以有向算子树呈现执行过程。每个算子节点上最值得关注的不是“总耗时”,而是三个量:输出行数(Output Rows)、执行时间占比、以及是否发生溢出(Spilling)。读图顺序建议自底向上:先看最底层的 TableScan,确认分区裁剪是否生效;再看 Join 与 Aggregate,判断是否存在数据倾斜或大表广播;最后看顶层的 Sort/Result,确认是否有不必要的排序。
典型瓶颈信号有三类。第一类是 TableScan 输出行数远超预期,说明过滤条件没有下推或分区裁剪失效,常见于对分区列使用函数包裹(如 WHERE TO_DATE(ts) = '2024-01-01')。第二类是 某个 Join 节点耗时占比超过 60%,且左右输入行数差异巨大,往往是 Join 顺序不合理或缺少有效的连接键。第三类是 出现 “Bytes spilled to local/remote storage”,说明该算子内存不足,需要检查 Warehouse 规格或减少中间结果集。
三、Query History 与 ACCOUNT_USAGE:从单条到全局
Query Profile 只能看一条查询,要回答“过去一周哪些查询最耗资源”,必须用 Query History 或 ACCOUNT_USAGE。Query History 在 Snowflake UI 中保留约 14 天,适合快速排查;ACCOUNT_USAGE 的 QUERY_HISTORY 视图保留最长 365 天,适合做趋势分析和容量治理。两者字段基本一致,但 ACCOUNT_USAGE 有约 45 分钟到 3 小时的延迟,不适合实时排障。
下面这段 SQL 用于找出过去 7 天内总耗时最高的 20 条查询,并区分“执行时间”与“排队时间”——这是判断瓶颈在计算还是在资源争抢的关键:
SELECT
query_id,
user_name,
warehouse_name,
warehouse_size,
total_elapsed_time / 1000 AS total_sec,
execution_time / 1000 AS exec_sec,
queued_provisioning_time / 1000 AS queue_sec,
bytes_scanned / POWER(1024, 3) AS gb_scanned,
partitions_scanned,
partitions_total,
query_text
FROM snowflake.account_usage.query_history
WHERE start_time >= DATEADD('day', -7, CURRENT_TIMESTAMP())
AND execution_status = 'SUCCESS'
ORDER BY total_elapsed_time DESC
LIMIT 20;
读结果时重点看两个比值:exec_sec / total_sec 若明显小于 1,说明大量时间花在排队或编译上,问题在 Warehouse 并发而非 SQL 本身;partitions_scanned / partitions_total 若接近 1,说明分区裁剪几乎没起作用,应优先检查过滤条件。这两个比值把“慢”拆成了可归因的两类原因。
四、EXPLAIN:执行前的计划验证
EXPLAIN 不执行查询,只返回优化器生成的逻辑计划,因此不消耗计算资源,非常适合在改 SQL 前做快速验证。它的输出是一棵算子树,关键看三点:是否出现 TableScan 上的 partitionsTotal 与 partitionsAssigned 差异、Join 的类型(HashJoin / BroadcastJoin / NestedLoopJoin)、以及是否出现 Sort 或 Aggregate 等阻塞算子。
EXPLAIN USING TEXT
SELECT c.region, SUM(o.amount)
FROM orders o
JOIN customers c ON o.cust_id = c.id
WHERE o.order_date >= '2024-01-01'
GROUP BY c.region;
若计划中 partitionsAssigned 等于 partitionsTotal,说明分区裁剪未生效;若出现 NestedLoopJoin,通常意味着连接条件缺失或使用了不等值连接,这类计划在大表上会急剧退化。EXPLAIN 的价值在于“改之前先看”,避免反复执行昂贵查询来试错。
五、瓶颈定位的实操闭环
把上述工具串成一条可复用的排查路径:先用 Query History 按 total_elapsed_time 排序锁定目标查询;打开其 Query Profile,定位耗时占比最高的算子;若怀疑是计划问题,用 EXPLAIN 验证分区裁剪与 Join 类型;若怀疑是资源争抢,回到 ACCOUNT_USAGE 查看该 Warehouse 同期的 WAREHOUSE_LOAD 与排队情况。最后根据归因结果选择动作:SQL 重写、聚簇键调整、Warehouse 规格升降或并发策略调整。
queued_provisioning_time 与 queued_overload_time。两者对应完全不同的优化手段,混为一谈会导致改错方向。QUERY_HISTORY 函数或 UI 中的 Query History,二者数据更及时但保留期更短。