Answers:
Explaining_EXPLAIN.pdf也可以提供帮助。
我总是感到困惑的部分是启动成本与总成本。每当我忘记它时,我都会使用Google,这使我回到这里,但并不能解释两者之间的区别,这就是我编写此答案的原因。这就是我从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的图形说明:
(使用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 SELECT * FROM tenk1 WHERE unique1 < 100 AND unique2 > 9000 LIMIT 2;
示例。EXPLAIN
在上面的示例中,两个组件的启动时间均为零,因为这两个组件在开始写入行之前都不需要进行任何处理:顺序扫描读取表的第一行并将其发出。在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)
并以图形方式:
再次,顺序扫描post
没有启动成本:它立即开始输出行。但是排序有很大的启动成本,23283.24
因为它必须对整个表进行排序,然后才能输出单行。排序的总成本23859.27
仅略高于启动成本,这反映了以下事实:一旦对整个数据集进行了排序,就可以非常快速地发出已排序的数据。
请注意,的启动时间LIMIT
23283.24
与排序的启动时间完全相等。这不是因为LIMIT
它本身具有很高的启动时间。实际上,它本身的启动时间为零,但是EXPLAIN
汇总了每个父级的所有子级成本,因此LIMIT
启动时间包括其子级的总启动时间。
成本的这种汇总可能使您难以理解每个单独组件的执行成本。例如,我们的LIMIT
启动时间为零,但乍一看并不明显。出于这个原因,其他几个人挂explain.depesz.com,由休伯特Lubaczewski(又名depesz),有助于理解创建的工具EXPLAIN
的-除其他事项外-减去从父成本孩子的费用。他在一篇有关他的工具的简短博客文章中提到了其他一些复杂性。
它从最大缩进到最小缩进执行,我相信从计划的底部到顶部。(因此,如果有两个缩进部分,则首先执行页面缩进部分,然后当它们遇到另一部分时执行,然后执行连接它们的规则。)
这个想法是,在每个步骤中,都有1或2个数据集到达并通过某种规则进行处理。如果只是一个数据集,则对该数据集执行该操作。(例如,扫描索引以找出所需的行,过滤数据集或对其进行排序。)如果是两个,则这两个数据集是缩进的两件事,并且按照您看到的规则将它们结合在一起。可以很容易地猜出大多数规则的含义(特别是如果您以前阅读过很多解释计划的话),但是您可以尝试通过查看文档或(更轻松地)通过将短语放入Google以及一些关键字,例如EXPLAIN
。
这显然不是一个完整的解释,但是它提供了足够的上下文,您通常可以确定所需的内容。例如,从实际数据库中考虑此计划:
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
,然后使用快速排序对数据集进行排序。
然后,它将两个数据集合并。(合并联接是一种“压缩”操作,其中它使两个排序的数据集并行移动,并在它们匹配时发出联接的行。)
就像我说的那样,您要遍历计划的内部到外部,从下到上。
PostgreSQL的官方文档对如何理解explain的输出提供了有趣而详尽的解释。