4. 查询性能分析工具

当一条查询在 Snowflake 上运行缓慢时,最忌讳的做法是凭直觉改 SQL 或盲目加大 Warehouse。正确的路径是:先用工具把“慢在哪里”量化出来,再决定是改 SQL、调 Warehouse、改数据布局,还是调整并发策略。本节围绕四类核心工具展开——Query Profile(图形化算子级剖析)、Query History(历史执行记录)、ACCOUNT_USAGE 视图(可聚合的元数据仓库)、EXPLAIN(执行前计划),并给出把它们串起来的瓶颈定位方法。

一、四类工具的分工与数据来源

这四类工具并非互相替代,而是覆盖“执行前—执行中—执行后—长期趋势”四个时间维度。理解它们各自的数据来源和粒度,是选对工具的前提。

查询性能分析工具链:时间维度与数据来源 执行前 执行中 执行后 长期趋势 EXPLAIN 执行前计划 算子树 / 分区裁剪 Join 顺序 / 估算行数 不消耗计算资源 无真实耗时 Query Profile 单次查询算子级剖析 各算子耗时占比 行数 / 溢出 / 倾斜 来自查询执行统计 最细粒度 Query History 近 14 天执行记录 状态 / 耗时 / 排队 Warehouse / 用户 UI 与 SQL 均可查 保留期有限 ACCOUNT_USAGE 可聚合元数据视图 QUERY_HISTORY WAREHOUSE_LOAD 最长 365 天 有延迟(约 45 分钟~3 小时) 瓶颈定位方法:从“哪条慢”到“为什么慢” ① 筛选慢查询 Query History 按耗时/排队排序 ② 看算子耗时 Query Profile 找最重算子 ③ 验证计划 EXPLAIN 裁剪/Join 是否合理 ④ 看长期趋势 ACCOUNT_USAGE 聚合/回归对比 ⑤ 归因与动作 SQL / 布局 / 规格 / 并发策略

二、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 规格升降或并发策略调整。

排查时先区分“执行慢”与“排队慢”。前者看 Query Profile 的算子耗时,后者看 Query History 的 queued_provisioning_time 与 queued_overload_time。两者对应完全不同的优化手段,混为一谈会导致改错方向。
ACCOUNT_USAGE 视图存在延迟,不要用它做实时故障定位;实时排查应使用 Information Schema 的 QUERY_HISTORY 函数或 UI 中的 Query History,二者数据更及时但保留期更短。