Hello! Recently I got few times into situation where I was trying to find out what is blocking DELETE queries. Running EXPLAIN (even VERBOSE one) wasn't useful, since the reason was slow trigger (missing index on foreign key column). I had to create testing entry and run EXPLAIN ANALYZE DELETE to get this information.
It will be really valuable for me to show triggers in EXPLAIN query since it will make clear for me there will be any trigger "activated" during execution of DELETE query and that can be the reason for slow DELETE. I have seen initial discussion at https://www.postgresql.org/message-id/flat/20693.1111732761%40sss.pgh.pa.us to show time spent in triggers in EXPLAIN ANALYZE including quick discussion to possibly show triggers during EXPLAIN. Anyway since it doesn't show any additional cost and just inform about the possibilities, I still consider this feature useful. This is probably implementation of idea mentioned at https://www.postgresql.org/message-id/21221.1111736869%40sss.pgh.pa.us by Tom Lane. After initial discussion with Pavel Stěhule and Tomáš Vondra at czech postgresql maillist ( https://groups.google.com/forum/#!topic/postgresql-cz/Dq1sT7huVho) I was able to prepare initial version of this patch. I have added EXPLAIN option called TRIGGERS enabled by default.There's already autoexplain property for this. I understand it is not possible to show only triggers which will be really activated unless query is really executed. EXPLAIN ANALYZE remains untouched with this patch. - patch with examples can be found at https://github.com/simi/postgres/pull/2 - DIFF format https://github.com/simi/postgres/pull/2.diff - PATCH format (also attached) https://github.com/simi/postgres/pull/2.patch All regression tests passed with this change locally on latest git master. I would like to cover this patch with more regression tests, but I wasn't sure where to place them, since there's no "EXPLAIN" related test "group". Is "src/test/regress/sql/triggers.sql" the best place to add tests related to this change? PS: This is my first try to contribute to postgresql codebase. The quality of patch is probably not the best, but I will be more than happy to do any requested change if needed. Regards, Josef Šimánek
From 18578e3d07aa159631e0903abae496a6482fa279 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Josef=20=C5=A0im=C3=A1nek?= <josef.sima...@gmail.com> Date: Sat, 27 Jul 2019 09:27:41 +0200 Subject: [PATCH] Show possible triggers in EXPLAIN. --- doc/src/sgml/ref/explain.sgml | 9 ++++ src/backend/commands/explain.c | 43 +++++++++++++++---- src/include/commands/explain.h | 1 + src/test/regress/expected/foreign_key.out | 6 ++- src/test/regress/expected/insert_conflict.out | 4 +- src/test/regress/expected/updatable_views.out | 10 ++++- 6 files changed, 60 insertions(+), 13 deletions(-) diff --git a/doc/src/sgml/ref/explain.sgml b/doc/src/sgml/ref/explain.sgml index 385d10411fa0..ba0d8df88c19 100644 --- a/doc/src/sgml/ref/explain.sgml +++ b/doc/src/sgml/ref/explain.sgml @@ -43,6 +43,7 @@ EXPLAIN [ ANALYZE ] [ VERBOSE ] <replaceable class="parameter">statement</replac BUFFERS [ <replaceable class="parameter">boolean</replaceable> ] TIMING [ <replaceable class="parameter">boolean</replaceable> ] SUMMARY [ <replaceable class="parameter">boolean</replaceable> ] + TRIGGERS [ <replaceable class="parameter">boolean</replaceable> ] FORMAT { TEXT | XML | JSON | YAML } </synopsis> </refsynopsisdiv> @@ -224,6 +225,14 @@ ROLLBACK; </listitem> </varlistentry> + <varlistentry> + <term><literal>TRIGGERS</literal></term> + <listitem> + <para> + TODO + </para> + </listitem> + </varlistentry> <varlistentry> <term><literal>FORMAT</literal></term> <listitem> diff --git a/src/backend/commands/explain.c b/src/backend/commands/explain.c index 62fb3434a32f..93845bcaad36 100644 --- a/src/backend/commands/explain.c +++ b/src/backend/commands/explain.c @@ -149,6 +149,7 @@ ExplainQuery(ParseState *pstate, ExplainStmt *stmt, const char *queryString, ListCell *lc; bool timing_set = false; bool summary_set = false; + bool triggers_set = false; /* Parse options list. */ foreach(lc, stmt->options) @@ -175,6 +176,11 @@ ExplainQuery(ParseState *pstate, ExplainStmt *stmt, const char *queryString, summary_set = true; es->summary = defGetBoolean(opt); } + else if (strcmp(opt->defname, "triggers") == 0) + { + triggers_set = true; + es->triggers = defGetBoolean(opt); + } else if (strcmp(opt->defname, "format") == 0) { char *p = defGetString(opt); @@ -210,6 +216,9 @@ ExplainQuery(ParseState *pstate, ExplainStmt *stmt, const char *queryString, /* if the timing was not set explicitly, set default value */ es->timing = (timing_set) ? es->timing : es->analyze; + /* if the triggers was not set explicitly, set default value */ + es->triggers = (triggers_set) ? es->triggers : true; + /* check that timing is used with EXPLAIN ANALYZE */ if (es->timing && !es->analyze) ereport(ERROR, @@ -556,7 +565,7 @@ ExplainOnePlan(PlannedStmt *plannedstmt, IntoClause *into, ExplainState *es, } /* Print info about runtime of triggers */ - if (es->analyze) + if (es->triggers) ExplainPrintTriggers(es, queryDesc); /* @@ -911,17 +920,23 @@ report_triggers(ResultRelInfo *rInfo, bool show_relname, ExplainState *es) { int nt; - if (!rInfo->ri_TrigDesc || !rInfo->ri_TrigInstrument) + if (!rInfo->ri_TrigDesc || (es->analyze && !rInfo->ri_TrigInstrument)) return; for (nt = 0; nt < rInfo->ri_TrigDesc->numtriggers; nt++) { Trigger *trig = rInfo->ri_TrigDesc->triggers + nt; - Instrumentation *instr = rInfo->ri_TrigInstrument + nt; + Instrumentation *instr; char *relname; char *conname = NULL; /* Must clean up instrumentation state */ - InstrEndLoop(instr); + if (es->analyze) { + instr = rInfo->ri_TrigInstrument + nt; + InstrEndLoop(instr); + + if (instr->ntuples == 0) + continue; + } /* * We ignore triggers that were never invoked; they likely aren't @@ -951,11 +966,15 @@ report_triggers(ResultRelInfo *rInfo, bool show_relname, ExplainState *es) appendStringInfo(es->str, " for constraint %s", conname); if (show_relname) appendStringInfo(es->str, " on %s", relname); - if (es->timing) - appendStringInfo(es->str, ": time=%.3f calls=%.0f\n", - 1000.0 * instr->total, instr->ntuples); + if (es->analyze) + { + if (es->timing) + appendStringInfo(es->str, ": time=%.3f calls=%.0f", + 1000.0 * instr->total, instr->ntuples); else - appendStringInfo(es->str, ": calls=%.0f\n", instr->ntuples); + appendStringInfo(es->str, ": calls=%.0f", instr->ntuples); + } + appendStringInfoString(es->str, "\n"); } else { @@ -966,7 +985,13 @@ report_triggers(ResultRelInfo *rInfo, bool show_relname, ExplainState *es) if (es->timing) ExplainPropertyFloat("Time", "ms", 1000.0 * instr->total, 3, es); - ExplainPropertyFloat("Calls", NULL, instr->ntuples, 0, es); + if (es->analyze) + { + if (es->timing) + ExplainPropertyFloat("Time", "ms", 1000.0 * instr->total, 3, + es); + ExplainPropertyFloat("Calls", NULL, instr->ntuples, 0, es); + } } if (conname) diff --git a/src/include/commands/explain.h b/src/include/commands/explain.h index 8639891c1643..7c67e4745b00 100644 --- a/src/include/commands/explain.h +++ b/src/include/commands/explain.h @@ -35,6 +35,7 @@ typedef struct ExplainState bool buffers; /* print buffer usage */ bool timing; /* print detailed node timing */ bool summary; /* print total planning and execution timing */ + bool triggers; /* print triggers */ bool settings; /* print modified settings */ ExplainFormat format; /* output format */ /* state for output formatting --- not reset for each new plan tree */ diff --git a/src/test/regress/expected/foreign_key.out b/src/test/regress/expected/foreign_key.out index 894084f94f14..2eb68575de39 100644 --- a/src/test/regress/expected/foreign_key.out +++ b/src/test/regress/expected/foreign_key.out @@ -1421,11 +1421,15 @@ explain (costs off) delete from t1 where a = 1; Index Cond: (a = 1) -> Seq Scan on t2 Filter: (b = 1) + Trigger for constraint t2_b_fkey + Trigger for constraint t2_b_fkey Delete on t1 -> Index Scan using t1_pkey on t1 Index Cond: (a = 1) -(10 rows) + Trigger for constraint t2_b_fkey + Trigger for constraint t2_b_fkey +(14 rows) delete from t1 where a = 1; -- Test a primary key with attributes located in later attnum positions diff --git a/src/test/regress/expected/insert_conflict.out b/src/test/regress/expected/insert_conflict.out index 1338b2b23e17..94a0fefc3a41 100644 --- a/src/test/regress/expected/insert_conflict.out +++ b/src/test/regress/expected/insert_conflict.out @@ -218,7 +218,9 @@ explain (costs off, format json) insert into insertconflicttest values (0, 'Bilb "Parallel Aware": false + } + ] + - } + + }, + + "Triggers": [ + + ] + } + ] (1 row) diff --git a/src/test/regress/expected/updatable_views.out b/src/test/regress/expected/updatable_views.out index 8443c24f18b7..162fac6904c4 100644 --- a/src/test/regress/expected/updatable_views.out +++ b/src/test/regress/expected/updatable_views.out @@ -899,7 +899,10 @@ EXPLAIN (costs off) UPDATE rw_view2 SET a=3 WHERE a=2; Recheck Cond: (a > 0) -> Bitmap Index Scan on base_tbl_pkey Index Cond: (a > 0) -(7 rows) + Trigger rw_view1_del_trig + Trigger rw_view1_ins_trig + Trigger rw_view1_upd_trig +(10 rows) EXPLAIN (costs off) DELETE FROM rw_view2 WHERE a=2; QUERY PLAN @@ -911,7 +914,10 @@ EXPLAIN (costs off) DELETE FROM rw_view2 WHERE a=2; Recheck Cond: (a > 0) -> Bitmap Index Scan on base_tbl_pkey Index Cond: (a > 0) -(7 rows) + Trigger rw_view1_del_trig + Trigger rw_view1_ins_trig + Trigger rw_view1_upd_trig +(10 rows) DROP TABLE base_tbl CASCADE; NOTICE: drop cascades to 2 other objects