PostgreSQL DBA(13) - 自顶往下的方法阅读执行计划
一般来说,阅读执行计划通常采用自底往上的方法,这好比从树的某片叶子出发然后再到树枝再到树干、树根这么一种方法来了解一颗树,这种方法存在的问题是如果这颗树很大,那么就可能出现"只见叶子不见树干"难以把握整体的情况。这时候可以结合自顶往下的方法进行阅读,从而在整体把握整个执行计划。
一、基础知识
为了更好的理解和使用自顶往下的阅读方法,需要预先掌握一些基础知识.
计划节点类型
在PostgreSQL中,计划节点分为四类,分别是控制节点(Control Node)、扫描节点(ScanNode),物化节点(Materialization Node)、连接节点(Join Node) 。
1.控制节点:是一类用于处理特殊情况的节点,用于实现特殊的执行流程。例如,Result节点可用来表示INSERT语句中VALUES子句指定的将要插人的元组。
2.扫描节点:此类节点用于扫描表等对象以从中获取元组。例如,SeqScan节点用于顺序扫描一个表.毎次扫描一个元组。
3.物化节点:这类节点种类比较复杂,但它们有一个共同特点,即能够缓存执行结果到辅助存储中。物化节点会在第一次被执行时生成其中的所有结果元组,然后将这些结果元组缓存起来,等待其上层节点取用;而非物化节点则是每次被执行时生成一个结果元组并返回给上层节点。例如,Sort节点能够获取下层节点返回的所有元组并根据指定的属性进行排序,并将排序结果全部缓存起来,每次上层节点从Sort节点取元组时就从缓存中按顺序返回下一个元组。
4.连接节点:此类节点对应于关系代数中的连接操作,可以实现多种连接方式(条件连接、左连接、右连接、全连接、自然连接等),每种节点实现一种连接算法。例如,HashJoin实现了基于Hash的连接箅法。
为了方便起见,在此基础上进行推广,设置规则:如控制节点/物化节点的子节点为连接节点,则视为连接节点,否则视为非连接节点.
根据这条规则,可以把所有的节点分为两类,即连接节点和非连接节点.
二、自顶往下的方法
自顶往下的方法,顾名思义,从执行计划的最顶端/最外层进行阅读.
1.识别节点类型(非连接节点 vs 连接节点)
2.如为非连接节点,则识别该节点的具体类型(数据表扫描...),该分支结束
3.如为连接节点,则识别连接的outer端和inner端
3.1 对outer端递归应用1/2/3步骤
3.2 对inner端递归应用1/2/3步骤
下面举例说明,SQL脚本如下:
testdb=# explain verbose select dw.*,grjf.grbh,grjf.xm,grjf.ny,grjf.je testdb-# from t_dwxx dw,lateral (select gr.grbh,gr.xm,jf.ny,jf.je testdb(# from t_grxx gr inner join t_jfxx jf testdb(# on gr.dwbh = dw.dwbh testdb(# and gr.grbh = jf.grbh) grjftestdb-# where dw.dwbh in ('1001','1002')testdb-# order by dw.dwbh; QUERY PLAN ------------------------------------------------------------------------------------------------------- Nested Loop (cost=0.87..61.01 rows=20 width=47) Output: dw.dwmc, dw.dwbh, dw.dwdz, gr.grbh, gr.xm, jf.ny, jf.je -> Nested Loop (cost=0.58..53.88 rows=20 width=32) Output: dw.dwmc, dw.dwbh, dw.dwdz, gr.grbh, gr.xm -> Index Scan using t_dwxx_pkey on public.t_dwxx dw (cost=0.29..13.92 rows=2 width=20) Output: dw.dwmc, dw.dwbh, dw.dwdz Index Cond: ((dw.dwbh)::text = ANY ('{1001,1002}'::text[])) -> Index Scan using idx_t_grxx_dwbh on public.t_grxx gr (cost=0.29..19.88 rows=10 width=16) Output: gr.dwbh, gr.grbh, gr.xm, gr.xb, gr.nl Index Cond: ((gr.dwbh)::text = (dw.dwbh)::text) -> Index Scan using idx_t_jfxx_grbh on public.t_jfxx jf (cost=0.29..0.35 rows=1 width=20) Output: jf.grbh, jf.ny, jf.je Index Cond: ((jf.grbh)::text = (gr.grbh)::text)(13 rows)
1.识别节点类型: Nested Loop -> 连接节点
3.连接节点:识别outer端,即通常所说的驱动表(这里是Nested Loop)和inner端(Index Scan).
3.1 outer端为连接节点,类型为Nested Loop
递归应用1/2/3步骤,解析该Nested Loop
3.1.1 outer端为Index Scan on t_dwxx
3.1.2 inner端为Index Scan on t_grxx
3.2 inner端,递归应用1/2/3步骤,即Index Scan on t_jfxx
采用自顶往下的方法,可以从"大局"上对执行计划上进行把握,避免一开始就进入繁杂的细节之中.
三、参考资料
PgSQL · 最佳实践 · EXPLAIN 使用浅析
跟我一起读postgresql源码(九)