开发人员向 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"
但“从表格上”阅读文本中的计划非常困难且不清楚:
- 显示在节点中 按子树资源求和
也就是说,要了解执行特定节点花费了多少时间,或者从表中读取的数据从磁盘中读取了多少数据,您需要以某种方式从另一个节点中减去一个节点 - 需要节点时间 乘以循环
是的,减法并不是必须在“头部”完成的最复杂的操作 - 毕竟,执行时间表示为节点一次执行的平均值,并且可能有数百个节点 - 好吧,所有这些加在一起使我们无法回答主要问题 - 那么谁 “最薄弱的环节”?
当我们试图向数百名开发人员解释这一切时,我们意识到从外部来看,它看起来像这样:
这意味着我们需要...
工具
在其中,我们试图收集所有关键机制,以帮助根据计划和要求理解“谁应该受到责备以及该做什么”。 好吧,并与社区分享您的部分经验。
满足并使用——
计划的可见性
当这个计划看起来像这样的时候,是不是很容易理解呢?
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
不是真的
但像这样, 以缩写形式当关键指标分开后,就更清晰了:
但如果计划更复杂,他就会出手相救 饼图时间分布 按节点:
好吧,对于最困难的选择,他会急于提供帮助 进度图:
例如,在一些非常重要的情况下,一个计划可能有多个实际根:
结构线索
好吧,如果计划的整个结构及其痛点已经摆好并且可见,为什么不向开发商突出显示并用“俄语”解释它们呢?
我们已经收集了几十个这样的推荐模板。
逐行查询分析器
现在,如果将原始查询叠加到分析的计划上,您可以看到每个单独的语句花费了多少时间 - 如下所示:
...或者甚至像这样:
将参数替换到请求中
如果您不仅将请求“附加”到计划,还“附加”了日志 DETAIL 行中的参数,则还可以将其复制到以下选项之一:
- 在查询中进行值替换
用于在您的基础上直接执行并进一步分析SELECT 'const', 'param'::text;
- 通过 PREPARE/EXECUTE 进行值替换
当可以忽略参数部分时,模拟调度程序的工作 - 例如,在处理分区表时DEALLOCATE ALL; PREPARE q(text) AS SELECT 'const', $1::text; EXECUTE q('param'::text);
计划档案
粘贴、分析、与同事分享! 这些计划将保留存档,您可以稍后返回:
但如果您不想让其他人看到您的计划,请不要忘记选中“不在存档中发布”框。
在接下来的文章中,我将讨论分析计划时出现的困难和决策。
来源: habr.com