EXPLAIN 沉默的内容以及如何让它说话

开发人员向 DBA 提出的经典问题或企业主向 PostgreSQL 顾问提出的经典问题听起来几乎总是相同的: “为什么数据库上的请求需要这么长时间才能完成?”

传统的原因集:

  • 算法效率低下
    当您决定 JOIN 数万条记录上的多个 CTE 时
  • 过时的统计数据
    如果表中数据的实际分布已经与上次 ANALYZE 收集到的数据有很大差异
  • “插入”资源
    CPU 不再有足够的专用计算能力,千兆字节的内存不断被抽出,或者磁盘无法满足数据库的所有“需求”
  • 闭塞 来自竞争过程

如果阻塞很难捕获和分析,那么对于我们需要的其他一切 查询计划,可以使用以下方法获得 解释运算符 (当然,最好立即解释(分析、缓冲区)......)或 自动解释模块.

但是,正如同一文档中所述,

“理解计划是一门艺术,掌握它需要一定的经验……”

但如果您使用正确的工具,您也可以不用它!

查询计划通常是什么样的? 像这样的东西:

Index Scan using pg_class_relname_nsp_index on pg_class (actual time=0.049..0.050 rows=1 loops=1)
  Index Cond: (relname = $1)
  Filter: (oid = $0)
  Buffers: shared hit=4
  InitPlan 1 (returns $0,$1)
    ->  Limit (actual time=0.019..0.020 rows=1 loops=1)
          Buffers: shared hit=1
          ->  Seq Scan on pg_class pg_class_1 (actual time=0.015..0.015 rows=1 loops=1)
                Filter: (relkind = 'r'::"char")
                Rows Removed by Filter: 5
                Buffers: shared hit=1

或者像这样:

"Append  (cost=868.60..878.95 rows=2 width=233) (actual time=0.024..0.144 rows=2 loops=1)"
"  Buffers: shared hit=3"
"  CTE cl"
"    ->  Seq Scan on pg_class  (cost=0.00..868.60 rows=9972 width=537) (actual time=0.016..0.042 rows=101 loops=1)"
"          Buffers: shared hit=3"
"  ->  Limit  (cost=0.00..0.10 rows=1 width=233) (actual time=0.023..0.024 rows=1 loops=1)"
"        Buffers: shared hit=1"
"        ->  CTE Scan on cl  (cost=0.00..997.20 rows=9972 width=233) (actual time=0.021..0.021 rows=1 loops=1)"
"              Buffers: shared hit=1"
"  ->  Limit  (cost=10.00..10.10 rows=1 width=233) (actual time=0.117..0.118 rows=1 loops=1)"
"        Buffers: shared hit=2"
"        ->  CTE Scan on cl cl_1  (cost=0.00..997.20 rows=9972 width=233) (actual time=0.001..0.104 rows=101 loops=1)"
"              Buffers: shared hit=2"
"Planning Time: 0.634 ms"
"Execution Time: 0.248 ms"

但“从表格上”阅读文本中的计划非常困难且不清楚:

  • 显示在节点中 按子树资源求和
    也就是说,要了解执行特定节点花费了多少时间,或者从表中读取的数据从磁盘中读取了多少数据,您需要以某种方式从另一个节点中减去一个节点
  • 需要节点时间 乘以循环
    是的,减法并不是必须在“头部”完成的最复杂的操作 - 毕竟,执行时间表示为节点一次执行的平均值,并且可能有数百个节点
  • 好吧,所有这些加在一起使我们无法回答主要问题 - 那么谁 “最薄弱的环节”?

当我们试图向数百名开发人员解释这一切时,我们意识到从外部来看,它看起来像这样:

EXPLAIN 沉默的内容以及如何让它说话

这意味着我们需要...

工具

在其中,我们试图收集所有关键机制,以帮助根据计划和要求理解“谁应该受到责备以及该做什么”。 好吧,并与社区分享您的部分经验。
满足并使用—— 解释.tensor.ru

计划的可见性

当这个计划看起来像这样的时候,是不是很容易理解呢?

Seq Scan on pg_class (actual time=0.009..1.304 rows=6609 loops=1)
  Buffers: shared hit=263
Planning Time: 0.108 ms
Execution Time: 1.800 ms

不是真的

但像这样, 以缩写形式当关键指标分开后,就更清晰了:

EXPLAIN 沉默的内容以及如何让它说话

但如果计划更复杂,他就会出手相救 饼图时间分布 按节点:

EXPLAIN 沉默的内容以及如何让它说话

好吧,对于最困难的选择,他会急于提供帮助 进度图:

EXPLAIN 沉默的内容以及如何让它说话

例如,在一些非常重要的情况下,一个计划可能有多个实际根:

EXPLAIN 沉默的内容以及如何让它说话EXPLAIN 沉默的内容以及如何让它说话

结构线索

好吧,如果计划的整个结构及其痛点已经摆好并且可见,为什么不向开发商突出显示并用“俄语”解释它们呢?

EXPLAIN 沉默的内容以及如何让它说话我们已经收集了几十个这样的推荐模板。

逐行查询分析器

现在,如果将原始查询叠加到分析的计划上,您可以看到每个单独的语句花费了多少时间 - 如下所示:

EXPLAIN 沉默的内容以及如何让它说话

...或者甚至像这样:

EXPLAIN 沉默的内容以及如何让它说话

将参数替换到请求中

如果您不仅将请求“附加”到计划,还“附加”了日志 DETAIL 行中的参数,则还可以将其复制到以下选项之一:

  • 在查询中进行值替换
    用于在您的基础上直接执行并进一步分析

    SELECT 'const', 'param'::text;
  • 通过 PREPARE/EXECUTE 进行值替换
    当可以忽略参数部分时,模拟调度程序的工作 - 例如,在处理分区表时

    DEALLOCATE ALL;
    PREPARE q(text) AS SELECT 'const', $1::text;
    EXECUTE q('param'::text);
    

计划档案

粘贴、分析、与同事分享! 这些计划将保留存档,您可以稍后返回: 解释.tensor.ru/archive

但如果您不想让其他人看到您的计划,请不要忘记选中“不在存档中发布”框。

在接下来的文章中,我将讨论分析计划时出现的困难和决策。

来源: habr.com

添加评论