1
0
mirror of https://github.com/postgres/postgres.git synced 2025-05-06 19:59:18 +03:00

Adjust EXPLAIN's output for disabled nodes

c01743aa4 added EXPLAIN output to display the plan node's disabled_node
count whenever that count is above 0.  Seemingly, there weren't many
people who liked that output as each parent of a disabled node would
also have a "Disabled Nodes" output due to the way disabled_nodes is
accumulated towards the root plan node.  It was often hard and sometimes
impossible to figure out which nodes were disabled from looking at
EXPLAIN.  You might think it would be possible to manually add up the
numbers from the "Disabled Nodes" output of a given node's children to
figure out if that node has a higher disabled_nodes count than its
children, but that wouldn't have worked for Append and Merge Append nodes
if some disabled child nodes were run-time pruned during init plan.  Those
children are not displayed in EXPLAIN.

Here we attempt to improve this output by only showing "Disabled: true"
against only the nodes which are explicitly disabled themselves.  That
seems to be the output that's desired by the most people who voiced
their opinion.  This is done by summing up the disabled_nodes of the
given node's children and checking if that number is less than the
disabled_nodes of the current node.

This commit also fixes a bug in make_sort() which was neglecting to set
the Sort's disabled_nodes field.  This should have copied what was done
in cost_sort(), but it hadn't been updated.  With the new output, the
choice to not maintain that field properly was clearly wrong as the
disabled-ness of the node was attributed to the Sort's parent instead.

Reviewed-by: Laurenz Albe, Alena Rybakina
Discussion: https://postgr.es/m/9e4ad616bebb103ec2084bf6f724cfc739e7fabb.camel@cybertec.at
This commit is contained in:
David Rowley 2024-10-11 17:19:59 +13:00
parent c75c6f8d28
commit 161320b4b9
17 changed files with 147 additions and 50 deletions

View File

@ -1363,6 +1363,96 @@ ExplainPreScanNode(PlanState *planstate, Bitmapset **rels_used)
return planstate_tree_walker(planstate, ExplainPreScanNode, rels_used);
}
/*
* plan_is_disabled
* Checks if the given plan node type was disabled during query planning.
* This is evident by the disable_node field being higher than the sum of
* the disabled_node field from the plan's children.
*/
static bool
plan_is_disabled(Plan *plan)
{
int child_disabled_nodes;
/* The node is certainly not disabled if this is zero */
if (plan->disabled_nodes == 0)
return false;
child_disabled_nodes = 0;
/*
* Handle special nodes first. Children of BitmapOrs and BitmapAnds can't
* be disabled, so no need to handle those specifically.
*/
if (IsA(plan, Append))
{
ListCell *lc;
Append *aplan = (Append *) plan;
/*
* Sum the Append childrens' disabled_nodes. This purposefully
* includes any run-time pruned children. Ignoring those could give
* us the incorrect number of disabled nodes.
*/
foreach(lc, aplan->appendplans)
{
Plan *subplan = lfirst(lc);
child_disabled_nodes += subplan->disabled_nodes;
}
}
else if (IsA(plan, MergeAppend))
{
ListCell *lc;
MergeAppend *maplan = (MergeAppend *) plan;
/*
* Sum the MergeAppend childrens' disabled_nodes. This purposefully
* includes any run-time pruned children. Ignoring those could give
* us the incorrect number of disabled nodes.
*/
foreach(lc, maplan->mergeplans)
{
Plan *subplan = lfirst(lc);
child_disabled_nodes += subplan->disabled_nodes;
}
}
else if (IsA(plan, SubqueryScan))
child_disabled_nodes += ((SubqueryScan *) plan)->subplan->disabled_nodes;
else if (IsA(plan, CustomScan))
{
ListCell *lc;
CustomScan *cplan = (CustomScan *) plan;
foreach(lc, cplan->custom_plans)
{
Plan *subplan = lfirst(lc);
child_disabled_nodes += subplan->disabled_nodes;
}
}
else
{
/*
* Else, sum up disabled_nodes from the plan's inner and outer side.
*/
if (outerPlan(plan))
child_disabled_nodes += outerPlan(plan)->disabled_nodes;
if (innerPlan(plan))
child_disabled_nodes += innerPlan(plan)->disabled_nodes;
}
/*
* It's disabled if the plan's disable_nodes is higher than the sum of its
* child's plan disabled_nodes.
*/
if (plan->disabled_nodes > child_disabled_nodes)
return true;
return false;
}
/*
* ExplainNode -
* Appends a description of a plan tree to es->str
@ -1399,6 +1489,7 @@ ExplainNode(PlanState *planstate, List *ancestors,
ExplainWorkersState *save_workers_state = es->workers_state;
int save_indent = es->indent;
bool haschildren;
bool isdisabled;
/*
* Prepare per-worker output buffers, if needed. We'll append the data in
@ -1914,9 +2005,10 @@ ExplainNode(PlanState *planstate, List *ancestors,
if (es->format == EXPLAIN_FORMAT_TEXT)
appendStringInfoChar(es->str, '\n');
if (plan->disabled_nodes != 0)
ExplainPropertyInteger("Disabled Nodes", NULL, plan->disabled_nodes,
es);
isdisabled = plan_is_disabled(plan);
if (es->format != EXPLAIN_FORMAT_TEXT || isdisabled)
ExplainPropertyBool("Disabled", isdisabled, es);
/* prepare per-worker general execution details */
if (es->workers_state && es->verbose)

View File

@ -6149,6 +6149,7 @@ make_sort(Plan *lefttree, int numCols,
plan = &node->plan;
plan->targetlist = lefttree->targetlist;
plan->disabled_nodes = lefttree->disabled_nodes + (enable_sort == false);
plan->qual = NIL;
plan->lefttree = lefttree;
plan->righttree = NULL;

View File

@ -2944,23 +2944,20 @@ GROUP BY c1.w, c1.z;
QUERY PLAN
-----------------------------------------------------
GroupAggregate
Disabled Nodes: 2
Group Key: c1.w, c1.z
-> Sort
Disabled Nodes: 2
Sort Key: c1.w, c1.z, c1.x, c1.y
-> Merge Join
Disabled Nodes: 2
Merge Cond: (c1.x = c2.x)
-> Sort
Sort Key: c1.x
-> Seq Scan on group_agg_pk c1
Disabled Nodes: 1
Disabled: true
-> Sort
Sort Key: c2.x
-> Seq Scan on group_agg_pk c2
Disabled Nodes: 1
(17 rows)
Disabled: true
(14 rows)
SELECT avg(c1.f ORDER BY c1.x, c1.y)
FROM group_agg_pk c1 JOIN group_agg_pk c2 ON c1.x = c2.x
@ -2982,24 +2979,21 @@ GROUP BY c1.y,c1.x,c2.x;
QUERY PLAN
-----------------------------------------------------
Group
Disabled Nodes: 2
Group Key: c1.x, c1.y
-> Incremental Sort
Disabled Nodes: 2
Sort Key: c1.x, c1.y
Presorted Key: c1.x
-> Merge Join
Disabled Nodes: 2
Merge Cond: (c1.x = c2.x)
-> Sort
Sort Key: c1.x
-> Seq Scan on group_agg_pk c1
Disabled Nodes: 1
Disabled: true
-> Sort
Sort Key: c2.x
-> Seq Scan on group_agg_pk c2
Disabled Nodes: 1
(18 rows)
Disabled: true
(15 rows)
EXPLAIN (COSTS OFF)
SELECT c1.y,c1.x FROM group_agg_pk c1
@ -3009,24 +3003,21 @@ GROUP BY c1.y,c2.x,c1.x;
QUERY PLAN
-----------------------------------------------------
Group
Disabled Nodes: 2
Group Key: c2.x, c1.y
-> Incremental Sort
Disabled Nodes: 2
Sort Key: c2.x, c1.y
Presorted Key: c2.x
-> Merge Join
Disabled Nodes: 2
Merge Cond: (c1.x = c2.x)
-> Sort
Sort Key: c1.x
-> Seq Scan on group_agg_pk c1
Disabled Nodes: 1
Disabled: true
-> Sort
Sort Key: c2.x
-> Seq Scan on group_agg_pk c2
Disabled Nodes: 1
(18 rows)
Disabled: true
(15 rows)
RESET enable_nestloop;
RESET enable_hashjoin;

View File

@ -335,12 +335,11 @@ select proname from pg_proc where proname ilike 'ri%foo' order by 1;
QUERY PLAN
----------------------------------------------
Sort
Disabled Nodes: 1
Sort Key: proname
-> Seq Scan on pg_proc
Disabled Nodes: 1
Disabled: true
Filter: (proname ~~* 'ri%foo'::text)
(6 rows)
(5 rows)
reset enable_seqscan;
reset enable_indexscan;

View File

@ -989,7 +989,7 @@ select * from collate_test1 where b ilike 'abc';
QUERY PLAN
-------------------------------
Seq Scan on collate_test1
Disabled Nodes: 1
Disabled: true
Filter: (b ~~* 'abc'::text)
(3 rows)
@ -1005,7 +1005,7 @@ select * from collate_test1 where b ilike 'ABC';
QUERY PLAN
-------------------------------
Seq Scan on collate_test1
Disabled Nodes: 1
Disabled: true
Filter: (b ~~* 'ABC'::text)
(3 rows)

View File

@ -104,6 +104,7 @@ select explain_filter('explain (analyze, buffers, format xml) select * from int8
<Actual-Total-Time>N.N</Actual-Total-Time> +
<Actual-Rows>N</Actual-Rows> +
<Actual-Loops>N</Actual-Loops> +
<Disabled>false</Disabled> +
<Shared-Hit-Blocks>N</Shared-Hit-Blocks> +
<Shared-Read-Blocks>N</Shared-Read-Blocks> +
<Shared-Dirtied-Blocks>N</Shared-Dirtied-Blocks>+
@ -152,6 +153,7 @@ select explain_filter('explain (analyze, serialize, buffers, format yaml) select
Actual Total Time: N.N +
Actual Rows: N +
Actual Loops: N +
Disabled: false +
Shared Hit Blocks: N +
Shared Read Blocks: N +
Shared Dirtied Blocks: N +
@ -213,6 +215,7 @@ select explain_filter('explain (buffers, format json) select * from int8_tbl i8'
"Total Cost": N.N, +
"Plan Rows": N, +
"Plan Width": N, +
"Disabled": false, +
"Shared Hit Blocks": N, +
"Shared Read Blocks": N, +
"Shared Dirtied Blocks": N, +
@ -262,6 +265,7 @@ select explain_filter('explain (analyze, buffers, format json) select * from int
"Actual Total Time": N.N, +
"Actual Rows": N, +
"Actual Loops": N, +
"Disabled": false, +
"Shared Hit Blocks": N, +
"Shared Read Blocks": N, +
"Shared Dirtied Blocks": N, +
@ -370,6 +374,7 @@ select explain_filter('explain (memory, summary, format yaml) select * from int8
Total Cost: N.N +
Plan Rows: N +
Plan Width: N +
Disabled: false +
Planning: +
Memory Used: N +
Memory Allocated: N +
@ -394,7 +399,8 @@ select explain_filter('explain (memory, analyze, format json) select * from int8
"Actual Startup Time": N.N, +
"Actual Total Time": N.N, +
"Actual Rows": N, +
"Actual Loops": N +
"Actual Loops": N, +
"Disabled": false +
}, +
"Planning": { +
"Memory Used": N, +
@ -497,6 +503,7 @@ select jsonb_pretty(
"string4" +
], +
"Schema": "public", +
"Disabled": false, +
"Node Type": "Seq Scan", +
"Plan Rows": 0, +
"Plan Width": 0, +
@ -540,6 +547,7 @@ select jsonb_pretty(
"stringu2", +
"string4" +
], +
"Disabled": false, +
"Sort Key": [ +
"tenk1.tenthous" +
], +
@ -586,6 +594,7 @@ select jsonb_pretty(
"stringu2", +
"string4" +
], +
"Disabled": false, +
"Node Type": "Gather Merge", +
"Plan Rows": 0, +
"Plan Width": 0, +

View File

@ -538,6 +538,7 @@ select jsonb_pretty(explain_analyze_inc_sort_nodes_without_memory('select * from
-------------------------------------------------
[ +
{ +
"Disabled": false, +
"Sort Key": [ +
"t.a", +
"t.b" +
@ -701,19 +702,17 @@ explain (costs off) select * from t left join (select * from (select * from t or
QUERY PLAN
------------------------------------------------
Nested Loop Left Join
Disabled Nodes: 1
Join Filter: (t_1.a = t.a)
-> Seq Scan on t
Filter: (a = ANY ('{1,2}'::integer[]))
-> Incremental Sort
Disabled Nodes: 1
Sort Key: t_1.a, t_1.b
Presorted Key: t_1.a
-> Sort
Disabled Nodes: 1
Disabled: true
Sort Key: t_1.a
-> Seq Scan on t t_1
(13 rows)
(11 rows)
select * from t left join (select * from (select * from t order by a) v order by a, b) s on s.a = t.a where t.a in (1, 2);
a | b | a | b
@ -744,6 +743,7 @@ select jsonb_pretty(explain_analyze_inc_sort_nodes_without_memory('select * from
-------------------------------------------------
[ +
{ +
"Disabled": false, +
"Sort Key": [ +
"t.a", +
"t.b" +

View File

@ -1614,7 +1614,6 @@ explain (verbose, costs off) select * from matest0 order by 1-id;
QUERY PLAN
------------------------------------------------------------------------
Merge Append
Disabled Nodes: 1
Sort Key: ((1 - matest0.id))
-> Index Scan using matest0i on public.matest0 matest0_1
Output: matest0_1.id, matest0_1.name, (1 - matest0_1.id)
@ -1624,11 +1623,11 @@ explain (verbose, costs off) select * from matest0 order by 1-id;
Output: matest0_3.id, matest0_3.name, ((1 - matest0_3.id))
Sort Key: ((1 - matest0_3.id))
-> Seq Scan on public.matest2 matest0_3
Disabled Nodes: 1
Disabled: true
Output: matest0_3.id, matest0_3.name, (1 - matest0_3.id)
-> Index Scan using matest3i on public.matest3 matest0_4
Output: matest0_4.id, matest0_4.name, (1 - matest0_4.id)
(15 rows)
(14 rows)
select * from matest0 order by 1-id;
id | name

View File

@ -218,6 +218,7 @@ explain (costs off, format json) insert into insertconflicttest values (0, 'Bilb
"Async Capable": false, +
"Relation Name": "insertconflicttest", +
"Alias": "insertconflicttest", +
"Disabled": false, +
"Conflict Resolution": "UPDATE", +
"Conflict Arbiter Indexes": ["key_index"], +
"Conflict Filter": "(insertconflicttest.fruit <> 'Lime'::text)",+
@ -226,7 +227,8 @@ explain (costs off, format json) insert into insertconflicttest values (0, 'Bilb
"Node Type": "Result", +
"Parent Relationship": "Outer", +
"Parallel Aware": false, +
"Async Capable": false +
"Async Capable": false, +
"Disabled": false +
} +
] +
} +

View File

@ -8010,15 +8010,14 @@ SELECT t1.a FROM skip_fetch t1 LEFT JOIN skip_fetch t2 ON t2.a = 1 WHERE t2.a IS
QUERY PLAN
---------------------------------------------------------
Nested Loop Anti Join
Disabled Nodes: 1
-> Seq Scan on skip_fetch t1
Disabled Nodes: 1
Disabled: true
-> Materialize
-> Bitmap Heap Scan on skip_fetch t2
Recheck Cond: (a = 1)
-> Bitmap Index Scan on skip_fetch_a_idx
Index Cond: (a = 1)
(9 rows)
(8 rows)
SELECT t1.a FROM skip_fetch t1 LEFT JOIN skip_fetch t2 ON t2.a = 1 WHERE t2.a IS NULL;
a

View File

@ -303,16 +303,15 @@ SELECT * FROM strtest s1 INNER JOIN strtest s2 ON s1.n >= s2.n;', false);
explain_memoize
----------------------------------------------------------------------------------
Nested Loop (actual rows=24 loops=N)
Disabled Nodes: 1
-> Seq Scan on strtest s1 (actual rows=6 loops=N)
Disabled Nodes: 1
Disabled: true
-> Memoize (actual rows=4 loops=N)
Cache Key: s1.n
Cache Mode: binary
Hits: 3 Misses: 3 Evictions: Zero Overflows: 0 Memory Usage: NkB
-> Index Scan using strtest_n_idx on strtest s2 (actual rows=4 loops=N)
Index Cond: (n <= s1.n)
(10 rows)
(9 rows)
-- Ensure we get 3 hits and 3 misses
SELECT explain_memoize('
@ -320,16 +319,15 @@ SELECT * FROM strtest s1 INNER JOIN strtest s2 ON s1.t >= s2.t;', false);
explain_memoize
----------------------------------------------------------------------------------
Nested Loop (actual rows=24 loops=N)
Disabled Nodes: 1
-> Seq Scan on strtest s1 (actual rows=6 loops=N)
Disabled Nodes: 1
Disabled: true
-> Memoize (actual rows=4 loops=N)
Cache Key: s1.t
Cache Mode: binary
Hits: 3 Misses: 3 Evictions: Zero Overflows: 0 Memory Usage: NkB
-> Index Scan using strtest_t_idx on strtest s2 (actual rows=4 loops=N)
Index Cond: (t <= s1.t)
(10 rows)
(9 rows)
DROP TABLE strtest;
-- Ensure memoize works with partitionwise join

View File

@ -537,14 +537,11 @@ explain (costs off)
QUERY PLAN
------------------------------------------------------------
Aggregate
Disabled Nodes: 1
-> Nested Loop
Disabled Nodes: 1
-> Gather
Disabled Nodes: 1
Workers Planned: 4
-> Parallel Seq Scan on tenk2
Disabled Nodes: 1
Disabled: true
Filter: (thousand = 0)
-> Gather
Workers Planned: 4
@ -552,7 +549,7 @@ explain (costs off)
Recheck Cond: (hundred > 1)
-> Bitmap Index Scan on tenk1_hundred
Index Cond: (hundred > 1)
(16 rows)
(13 rows)
select count(*) from tenk1, tenk2 where tenk1.hundred > 1 and tenk2.thousand=0;
count

View File

@ -474,6 +474,7 @@ SELECT * FROM
"Async Capable": false, +
"Table Function Name": "json_table", +
"Alias": "json_table_func", +
"Disabled": false, +
"Output": ["id", "\"int\"", "text"], +
"Table Function Call": "JSON_TABLE('null'::jsonb, '$[*]' AS json_table_path_0 PASSING 3 AS a, '\"foo\"'::jsonb AS \"b c\" COLUMNS (id FOR ORDINALITY, \"int\" integer PATH '$', text text PATH '$'))"+
} +

View File

@ -822,7 +822,7 @@ explain (costs off) select '123'::xid union select '123'::xid;
QUERY PLAN
---------------------------
HashAggregate
Disabled Nodes: 1
Disabled: true
Group Key: ('123'::xid)
-> Append
-> Result

View File

@ -1577,6 +1577,7 @@ SELECT f.* FROM xmldata, LATERAL xmltable('/ROWS/ROW[COUNTRY_NAME="Japan" or COU
"Parallel Aware": false, +
"Async Capable": false, +
"Join Type": "Inner", +
"Disabled": false, +
"Output": ["f.\"COUNTRY_NAME\"", "f.\"REGION_ID\""], +
"Inner Unique": false, +
"Plans": [ +
@ -1588,6 +1589,7 @@ SELECT f.* FROM xmldata, LATERAL xmltable('/ROWS/ROW[COUNTRY_NAME="Japan" or COU
"Relation Name": "xmldata", +
"Schema": "public", +
"Alias": "xmldata", +
"Disabled": false, +
"Output": ["xmldata.data"] +
}, +
{ +
@ -1597,6 +1599,7 @@ SELECT f.* FROM xmldata, LATERAL xmltable('/ROWS/ROW[COUNTRY_NAME="Japan" or COU
"Async Capable": false, +
"Table Function Name": "xmltable", +
"Alias": "f", +
"Disabled": false, +
"Output": ["f.\"COUNTRY_NAME\"", "f.\"REGION_ID\""], +
"Table Function Call": "XMLTABLE(('/ROWS/ROW[COUNTRY_NAME=\"Japan\" or COUNTRY_NAME=\"India\"]'::text) PASSING (xmldata.data) COLUMNS \"COUNTRY_NAME\" text, \"REGION_ID\" integer)",+
"Filter": "(f.\"COUNTRY_NAME\" = 'Japan'::text)" +

View File

@ -1209,6 +1209,7 @@ SELECT f.* FROM xmldata, LATERAL xmltable('/ROWS/ROW[COUNTRY_NAME="Japan" or COU
"Parallel Aware": false, +
"Async Capable": false, +
"Join Type": "Inner", +
"Disabled": false, +
"Output": ["f.\"COUNTRY_NAME\"", "f.\"REGION_ID\""], +
"Inner Unique": false, +
"Plans": [ +
@ -1220,6 +1221,7 @@ SELECT f.* FROM xmldata, LATERAL xmltable('/ROWS/ROW[COUNTRY_NAME="Japan" or COU
"Relation Name": "xmldata", +
"Schema": "public", +
"Alias": "xmldata", +
"Disabled": false, +
"Output": ["xmldata.data"] +
}, +
{ +
@ -1229,6 +1231,7 @@ SELECT f.* FROM xmldata, LATERAL xmltable('/ROWS/ROW[COUNTRY_NAME="Japan" or COU
"Async Capable": false, +
"Table Function Name": "xmltable", +
"Alias": "f", +
"Disabled": false, +
"Output": ["f.\"COUNTRY_NAME\"", "f.\"REGION_ID\""], +
"Table Function Call": "XMLTABLE(('/ROWS/ROW[COUNTRY_NAME=\"Japan\" or COUNTRY_NAME=\"India\"]'::text) PASSING (xmldata.data) COLUMNS \"COUNTRY_NAME\" text, \"REGION_ID\" integer)",+
"Filter": "(f.\"COUNTRY_NAME\" = 'Japan'::text)" +

View File

@ -1563,6 +1563,7 @@ SELECT f.* FROM xmldata, LATERAL xmltable('/ROWS/ROW[COUNTRY_NAME="Japan" or COU
"Parallel Aware": false, +
"Async Capable": false, +
"Join Type": "Inner", +
"Disabled": false, +
"Output": ["f.\"COUNTRY_NAME\"", "f.\"REGION_ID\""], +
"Inner Unique": false, +
"Plans": [ +
@ -1574,6 +1575,7 @@ SELECT f.* FROM xmldata, LATERAL xmltable('/ROWS/ROW[COUNTRY_NAME="Japan" or COU
"Relation Name": "xmldata", +
"Schema": "public", +
"Alias": "xmldata", +
"Disabled": false, +
"Output": ["xmldata.data"] +
}, +
{ +
@ -1583,6 +1585,7 @@ SELECT f.* FROM xmldata, LATERAL xmltable('/ROWS/ROW[COUNTRY_NAME="Japan" or COU
"Async Capable": false, +
"Table Function Name": "xmltable", +
"Alias": "f", +
"Disabled": false, +
"Output": ["f.\"COUNTRY_NAME\"", "f.\"REGION_ID\""], +
"Table Function Call": "XMLTABLE(('/ROWS/ROW[COUNTRY_NAME=\"Japan\" or COUNTRY_NAME=\"India\"]'::text) PASSING (xmldata.data) COLUMNS \"COUNTRY_NAME\" text, \"REGION_ID\" integer)",+
"Filter": "(f.\"COUNTRY_NAME\" = 'Japan'::text)" +