PostgreSQL 到底告诉了我什么?

MySQL 的解释输出非常简单。PostgreSQL 的要稍微复杂一些。我也没能找到一个好的资源来解释这个问题。

你能描述一下具体解释是什么吗,或者至少给我指出一个好的资源的方向?

67687 次浏览

它执行从最缩进到最少缩进,我相信从计划的底部到顶部。(因此,如果有两个缩进的部分,那么页面下面的一个部分首先执行,然后当它们遇到另一个部分时执行,然后执行连接它们的规则。)

其思想是,在每个步骤中都有1或2个数据集到达并按照某种规则进行处理。如果只有一个数据集,则对该数据集执行该操作。(例如,通过扫描索引来确定需要哪些行、筛选数据集或对其进行排序。)如果有两个,那么这两个数据集就是进一步缩进的两个元素,并且它们通过您看到的规则连接在一起。大多数规则的含义可以很容易猜到(特别是如果你以前读过一大堆解释计划的话) ,但是你可以试着通过查看文档或者(更容易)通过把短语和一些像 EXPLAIN这样的关键词一起扔进 Google 来验证单个条目。

这显然不是一个完整的解释,但它提供了足够的上下文,您通常可以弄清楚您想要的任何内容。例如,考虑一下来自实际数据库的这个计划:

explain analyze
select a.attributeid, a.attributevalue, b.productid
from orderitemattribute a, orderitem b
where a.orderid = b.orderid
and a.attributeid = 'display-album'
and b.productid = 'ModernBook';


------------------------------------------------------------------------------------------------------------------------------------------------------------


Merge Join  (cost=125379.14..125775.12 rows=3311 width=29) (actual time=841.478..841.478 rows=0 loops=1)
Merge Cond: (a.orderid = b.orderid)
->  Sort  (cost=109737.32..109881.89 rows=57828 width=23) (actual time=736.163..774.475 rows=16815 loops=1)
Sort Key: a.orderid
Sort Method:  quicksort  Memory: 1695kB
->  Bitmap Heap Scan on orderitemattribute a  (cost=1286.88..105163.27 rows=57828 width=23) (actual time=41.536..612.731 rows=16815 loops=1)
Recheck Cond: ((attributeid)::text = 'display-album'::text)
->  Bitmap Index Scan on (cost=0.00..1272.43 rows=57828 width=0) (actual time=25.033..25.033 rows=16815 loops=1)
Index Cond: ((attributeid)::text = 'display-album'::text)
->  Sort  (cost=15641.81..15678.73 rows=14769 width=14) (actual time=14.471..16.898 rows=1109 loops=1)
Sort Key: b.orderid
Sort Method:  quicksort  Memory: 76kB
->  Bitmap Heap Scan on orderitem b  (cost=310.96..14619.03 rows=14769 width=14) (actual time=1.865..8.480 rows=1114 loops=1)
Recheck Cond: ((productid)::text = 'ModernBook'::text)
->  Bitmap Index Scan on id_orderitem_productid  (cost=0.00..307.27 rows=14769 width=0) (actual time=1.431..1.431 rows=1114 loops=1)
Index Cond: ((productid)::text = 'ModernBook'::text)
Total runtime: 842.134 ms
(17 rows)

试着自己读一读,看看是否有意义。

我读到的是,数据库首先扫描 id_orderitem_productid索引,使用它从 orderitem中找到它想要的行,然后使用快速排序对数据集进行排序(如果数据不适合 RAM,所使用的排序将发生变化) ,然后将其放在一边。

接下来,它扫描 orditematt_attributeid_idx以从 orderitemattribute中找到它想要的行,然后使用快速排序对数据集进行排序。

然后获取两个数据集并合并它们。(合并连接是一种“ zip”操作,它并行地遍历两个排序的数据集,当它们匹配时发出连接的行。)

就像我说的,你从计划的内部部分到外部部分,从底部到顶部。

PgAdmin 将向您显示解释计划的图形表示。在两者之间来回切换可以真正帮助您理解文本表示的含义。但是,如果您只是想知道它将要做什么,那么您可以始终使用 GUI。

还有一个在线辅助工具 德佩兹,它将突出显示分析结果中代价高昂的部分。

还有一个,这是 同样的结果对我来说问题在哪里更清楚了。

如果你安装了 pgadmin,有一个解释按钮,除了给出文本输出外,还会画出正在发生的事情的图表,显示过滤器、排序和子集合并,我发现这些对于查看正在发生的事情非常有用。

PostgreSQL 的官方文档 提供了一个有趣的、彻底的解释,说明如何理解 description 的输出。

我经常感到困惑的部分是启动成本和总成本。我每次忘了都会谷歌一下,然后又回到这里,但这并不能解释其中的区别,所以我写了这个答案。这是我从 Postgres EXPLAIN文档解释了我的理解。中得到的信息

下面是一个管理论坛的应用程序的例子:

EXPLAIN SELECT * FROM post LIMIT 50;


Limit  (cost=0.00..3.39 rows=50 width=422)
->  Seq Scan on post  (cost=0.00..15629.12 rows=230412 width=422)

下面是来自 PgAdmin 的图形解释:

graphical explanation of first query

(当您使用 PgAdmin 时,您可以将鼠标指向一个组件来读取成本详细信息。)

成本表示为元组,例如,LIMIT的成本是 cost=0.00..3.39,而顺序扫描 post的成本是 cost=0.00..15629.12。元组中的第一个数字是 启动成本,第二个数字是 总成本。因为我使用的是 EXPLAIN而不是 EXPLAIN ANALYZE,所以这些成本都是估计值,而不是实际测量值。

  • 启动成本是一个复杂的概念。它不仅仅表示组件 开始之前的时间量。它表示从组件开始执行(读取数据)到 组件输出它的第一行之间的时间量。
  • 总成本 是组件的整个执行时间,从开始读入数据到完成写入输出。

复杂的是,每个“父”节点的成本包括其子节点的成本。在文本表示中,树由缩进表示,例如 LIMIT是父节点,Seq Scan是它的子节点。在 PgAdmin 表示中,箭头指向从子级到父级ーー数据流的方向ーー如果您熟悉图论,这可能有违直觉。

文档说成本包含所有子节点,但是请注意,父 3.39的总成本要比子 15629.12的总成本小得多。总成本不包括在内,因为像 LIMIT这样的组件不需要处理它的整个输入。参见 Postgres EXPLAIN文档中的 EXPLAIN SELECT * FROM tenk1 WHERE unique1 < 100 AND unique2 > 9000 LIMIT 2;示例。

在上面的示例中,两个组件的启动时间都为零,因为在开始写行之前,两个组件都不需要执行任何处理: 顺序扫描读取表的第一行并发出它。LIMIT读取它的第一行,然后发出它。

组件什么时候需要进行大量处理才能开始输出任何行?有很多可能的原因,但是让我们看一个清晰的例子。下面是之前的同一个查询,但现在包含一个 ORDER BY子句:

EXPLAIN SELECT * FROM post ORDER BY body LIMIT 50;


Limit  (cost=23283.24..23283.37 rows=50 width=422)
->  Sort  (cost=23283.24..23859.27 rows=230412 width=422)
Sort Key: body
->  Seq Scan on post  (cost=0.00..15629.12 rows=230412 width=422)

形象地说:

graphical explanation of second query

同样,post上的顺序扫描没有启动成本: 它立即开始输出行。但是这种排序有一个重要的启动成本 23283.24,因为它必须 在输出一行之前对整个表进行排序。排序 23859.27的总成本仅略高于启动成本,这反映出一旦对整个数据集进行了排序,排序后的数据可以非常快速地发出。

请注意,LIMIT 23283.24的启动时间恰好等于排序的启动时间。这并不是因为 LIMIT本身的启动时间很长。它本身实际上没有启动时间,但是 EXPLAIN为每个父级卷起所有子级成本,因此 LIMIT的启动时间包括子级的总启动时间。

这种成本汇总会导致难以理解每个单独组件的执行成本。例如,我们的 LIMIT没有启动时间,但是乍一看并不明显。出于这个原因,还有几个人与 Explain.depesz.com有关。 Explain.depesz.com是由休伯特 · 卢巴切夫斯基(Hubert Lubaczewski,又名 depesz)开发的一个工具,通过从父母成本中减去子女成本等方式帮助理解 EXPLAIN。他在 一篇简短的博客文章中提到了关于他的工具的其他一些复杂性。

Dalibo/pev2 是一个非常有用的可视化工具。

这里可以买到 https://explain.dalibo.com/

解释可视化工具2(PEV2)看起来类似于 Pev,但是没有主动维护。

这个项目是一个优秀的 Postgres 解释可视化重写 (pev)。荣誉去亚历克斯 Tatiyants。

Pev 项目最初是在2016年初编写的,但似乎是 从那时起就被遗弃了。超过3年没有任何活动 虽然有几个问题是公开的和相关的 请求暂缓执行。

enter image description here