Skip to content

Commit 4739a69

Browse files
committed
pg_stat_statements: Add rows_scanned column
Add a new rows_scanned column to pg_stat_statements that tracks the total number of rows scanned by scan nodes (SeqScan, IndexScan, IndexOnlyScan, BitmapHeapScan, etc.) before filter conditions are applied. This metric is collected by walking the plan tree and summing up ntuples + nfiltered1 for all scan nodes. This information is valuable for identifying queries that scan many rows but return few, which often indicates missing indexes or suboptimal query plans. Combined with the existing rows column, users can calculate the filtering efficiency of their queries. The new column appears after rows in the view, so existing queries that select specific columns by name will continue to work. Bump extension version to 1.14.
1 parent d625d77 commit 4739a69

File tree

5 files changed

+188
-2
lines changed

5 files changed

+188
-2
lines changed

contrib/pg_stat_statements/Makefile

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -7,6 +7,7 @@ OBJS = \
77

88
EXTENSION = pg_stat_statements
99
DATA = pg_stat_statements--1.4.sql \
10+
pg_stat_statements--1.13--1.14.sql \
1011
pg_stat_statements--1.12--1.13.sql \
1112
pg_stat_statements--1.11--1.12.sql pg_stat_statements--1.10--1.11.sql \
1213
pg_stat_statements--1.9--1.10.sql pg_stat_statements--1.8--1.9.sql \

contrib/pg_stat_statements/meson.build

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -21,6 +21,7 @@ contrib_targets += pg_stat_statements
2121
install_data(
2222
'pg_stat_statements.control',
2323
'pg_stat_statements--1.4.sql',
24+
'pg_stat_statements--1.13--1.14.sql',
2425
'pg_stat_statements--1.12--1.13.sql',
2526
'pg_stat_statements--1.11--1.12.sql',
2627
'pg_stat_statements--1.10--1.11.sql',
Lines changed: 79 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,79 @@
1+
/* contrib/pg_stat_statements/pg_stat_statements--1.13--1.14.sql */
2+
3+
-- complain if script is sourced in psql, rather than via ALTER EXTENSION
4+
\echo Use "ALTER EXTENSION pg_stat_statements UPDATE TO '1.14'" to load this file. \quit
5+
6+
/* First we have to remove them from the extension */
7+
ALTER EXTENSION pg_stat_statements DROP VIEW pg_stat_statements;
8+
ALTER EXTENSION pg_stat_statements DROP FUNCTION pg_stat_statements(boolean);
9+
10+
/* Then we can drop them */
11+
DROP VIEW pg_stat_statements;
12+
DROP FUNCTION pg_stat_statements(boolean);
13+
14+
/* Now redefine */
15+
CREATE FUNCTION pg_stat_statements(IN showtext boolean,
16+
OUT userid oid,
17+
OUT dbid oid,
18+
OUT toplevel bool,
19+
OUT queryid bigint,
20+
OUT query text,
21+
OUT plans int8,
22+
OUT total_plan_time float8,
23+
OUT min_plan_time float8,
24+
OUT max_plan_time float8,
25+
OUT mean_plan_time float8,
26+
OUT stddev_plan_time float8,
27+
OUT calls int8,
28+
OUT total_exec_time float8,
29+
OUT min_exec_time float8,
30+
OUT max_exec_time float8,
31+
OUT mean_exec_time float8,
32+
OUT stddev_exec_time float8,
33+
OUT rows int8,
34+
OUT rows_scanned int8,
35+
OUT shared_blks_hit int8,
36+
OUT shared_blks_read int8,
37+
OUT shared_blks_dirtied int8,
38+
OUT shared_blks_written int8,
39+
OUT local_blks_hit int8,
40+
OUT local_blks_read int8,
41+
OUT local_blks_dirtied int8,
42+
OUT local_blks_written int8,
43+
OUT temp_blks_read int8,
44+
OUT temp_blks_written int8,
45+
OUT shared_blk_read_time float8,
46+
OUT shared_blk_write_time float8,
47+
OUT local_blk_read_time float8,
48+
OUT local_blk_write_time float8,
49+
OUT temp_blk_read_time float8,
50+
OUT temp_blk_write_time float8,
51+
OUT wal_records int8,
52+
OUT wal_fpi int8,
53+
OUT wal_bytes numeric,
54+
OUT wal_buffers_full int8,
55+
OUT jit_functions int8,
56+
OUT jit_generation_time float8,
57+
OUT jit_inlining_count int8,
58+
OUT jit_inlining_time float8,
59+
OUT jit_optimization_count int8,
60+
OUT jit_optimization_time float8,
61+
OUT jit_emission_count int8,
62+
OUT jit_emission_time float8,
63+
OUT jit_deform_count int8,
64+
OUT jit_deform_time float8,
65+
OUT parallel_workers_to_launch int8,
66+
OUT parallel_workers_launched int8,
67+
OUT generic_plan_calls int8,
68+
OUT custom_plan_calls int8,
69+
OUT stats_since timestamp with time zone,
70+
OUT minmax_stats_since timestamp with time zone
71+
)
72+
RETURNS SETOF record
73+
AS 'MODULE_PATHNAME', 'pg_stat_statements_1_14'
74+
LANGUAGE C STRICT VOLATILE PARALLEL SAFE;
75+
76+
CREATE VIEW pg_stat_statements AS
77+
SELECT * FROM pg_stat_statements(true);
78+
79+
GRANT SELECT ON pg_stat_statements TO PUBLIC;

contrib/pg_stat_statements/pg_stat_statements.c

Lines changed: 106 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -56,6 +56,7 @@
5656
#include "jit/jit.h"
5757
#include "mb/pg_wchar.h"
5858
#include "miscadmin.h"
59+
#include "nodes/nodeFuncs.h"
5960
#include "nodes/queryjumble.h"
6061
#include "optimizer/planner.h"
6162
#include "parser/analyze.h"
@@ -116,6 +117,7 @@ typedef enum pgssVersion
116117
PGSS_V1_11,
117118
PGSS_V1_12,
118119
PGSS_V1_13,
120+
PGSS_V1_14,
119121
} pgssVersion;
120122

121123
typedef enum pgssStoreKind
@@ -166,6 +168,7 @@ typedef struct Counters
166168
double sum_var_time[PGSS_NUMKIND]; /* sum of variances in
167169
* planning/execution time in msec */
168170
int64 rows; /* total # of retrieved or affected rows */
171+
int64 rows_scanned; /* total # of rows scanned by scan nodes */
169172
int64 shared_blks_hit; /* # of shared buffer hits */
170173
int64 shared_blks_read; /* # of shared disk blocks read */
171174
int64 shared_blks_dirtied; /* # of shared disk blocks dirtied */
@@ -327,6 +330,7 @@ PG_FUNCTION_INFO_V1(pg_stat_statements_1_10);
327330
PG_FUNCTION_INFO_V1(pg_stat_statements_1_11);
328331
PG_FUNCTION_INFO_V1(pg_stat_statements_1_12);
329332
PG_FUNCTION_INFO_V1(pg_stat_statements_1_13);
333+
PG_FUNCTION_INFO_V1(pg_stat_statements_1_14);
330334
PG_FUNCTION_INFO_V1(pg_stat_statements);
331335
PG_FUNCTION_INFO_V1(pg_stat_statements_info);
332336

@@ -355,6 +359,7 @@ static void pgss_store(const char *query, int64 queryId,
355359
int query_location, int query_len,
356360
pgssStoreKind kind,
357361
double total_time, uint64 rows,
362+
int64 rows_scanned,
358363
const BufferUsage *bufusage,
359364
const WalUsage *walusage,
360365
const struct JitInstrumentation *jitusage,
@@ -878,6 +883,7 @@ pgss_post_parse_analyze(ParseState *pstate, Query *query, JumbleState *jstate)
878883
PGSS_INVALID,
879884
0,
880885
0,
886+
0,
881887
NULL,
882888
NULL,
883889
NULL,
@@ -960,6 +966,7 @@ pgss_planner(Query *parse,
960966
PGSS_PLAN,
961967
INSTR_TIME_GET_MILLISEC(duration),
962968
0,
969+
0,
963970
&bufusage,
964971
&walusage,
965972
NULL,
@@ -1071,6 +1078,74 @@ pgss_ExecutorFinish(QueryDesc *queryDesc)
10711078
PG_END_TRY();
10721079
}
10731080

1081+
/*
1082+
* Helper structure for collecting scan statistics
1083+
*/
1084+
typedef struct ScanStats
1085+
{
1086+
int64 rows_scanned; /* total rows scanned (before filtering) */
1087+
} ScanStats;
1088+
1089+
/*
1090+
* Determine if a node is a scan node that reads from storage.
1091+
* For scan nodes, we want to count tuples before filter conditions are applied.
1092+
*/
1093+
static inline bool
1094+
IsScanNode(PlanState *planstate)
1095+
{
1096+
switch (nodeTag(planstate))
1097+
{
1098+
case T_SeqScanState:
1099+
case T_SampleScanState:
1100+
case T_IndexScanState:
1101+
case T_IndexOnlyScanState:
1102+
case T_BitmapHeapScanState:
1103+
case T_TidScanState:
1104+
case T_TidRangeScanState:
1105+
case T_ForeignScanState:
1106+
case T_CustomScanState:
1107+
return true;
1108+
default:
1109+
return false;
1110+
}
1111+
}
1112+
1113+
/*
1114+
* Walker function to collect scan statistics from all nodes.
1115+
* For scan nodes, rows_scanned = tuples output + tuples filtered.
1116+
*/
1117+
static bool
1118+
pgss_collect_scan_stats_walker(PlanState *planstate, void *context)
1119+
{
1120+
ScanStats *stats = (ScanStats *) context;
1121+
1122+
if (planstate->instrument && IsScanNode(planstate))
1123+
{
1124+
/*
1125+
* For scan nodes, rows_scanned is the number of tuples produced plus
1126+
* the number of tuples filtered out by the scan's filter condition.
1127+
* This represents the total number of tuples read from storage.
1128+
*/
1129+
double scanned = planstate->instrument->ntuples +
1130+
planstate->instrument->nfiltered1;
1131+
1132+
stats->rows_scanned += (int64) scanned;
1133+
}
1134+
1135+
return planstate_tree_walker(planstate, pgss_collect_scan_stats_walker, context);
1136+
}
1137+
1138+
/*
1139+
* Collect scan statistics from the entire plan tree.
1140+
*/
1141+
static void
1142+
pgss_collect_scan_stats(PlanState *planstate, ScanStats *stats)
1143+
{
1144+
memset(stats, 0, sizeof(ScanStats));
1145+
if (planstate)
1146+
pgss_collect_scan_stats_walker(planstate, stats);
1147+
}
1148+
10741149
/*
10751150
* ExecutorEnd hook: store results if needed
10761151
*/
@@ -1082,19 +1157,28 @@ pgss_ExecutorEnd(QueryDesc *queryDesc)
10821157
if (queryId != INT64CONST(0) && queryDesc->totaltime &&
10831158
pgss_enabled(nesting_level))
10841159
{
1160+
ScanStats scan_stats;
1161+
10851162
/*
10861163
* Make sure stats accumulation is done. (Note: it's okay if several
10871164
* levels of hook all do this.)
10881165
*/
10891166
InstrEndLoop(queryDesc->totaltime);
10901167

1168+
/*
1169+
* Collect scan statistics from the plan tree. This must be done
1170+
* before standard_ExecutorEnd which will destroy the planstate.
1171+
*/
1172+
pgss_collect_scan_stats(queryDesc->planstate, &scan_stats);
1173+
10911174
pgss_store(queryDesc->sourceText,
10921175
queryId,
10931176
queryDesc->plannedstmt->stmt_location,
10941177
queryDesc->plannedstmt->stmt_len,
10951178
PGSS_EXEC,
10961179
queryDesc->totaltime->total * 1000.0, /* convert to msec */
10971180
queryDesc->estate->es_total_processed,
1181+
scan_stats.rows_scanned,
10981182
&queryDesc->totaltime->bufusage,
10991183
&queryDesc->totaltime->walusage,
11001184
queryDesc->estate->es_jit ? &queryDesc->estate->es_jit->instr : NULL,
@@ -1229,6 +1313,7 @@ pgss_ProcessUtility(PlannedStmt *pstmt, const char *queryString,
12291313
PGSS_EXEC,
12301314
INSTR_TIME_GET_MILLISEC(duration),
12311315
rows,
1316+
0, /* rows_scanned not available for utility statements */
12321317
&bufusage,
12331318
&walusage,
12341319
NULL,
@@ -1293,6 +1378,7 @@ pgss_store(const char *query, int64 queryId,
12931378
int query_location, int query_len,
12941379
pgssStoreKind kind,
12951380
double total_time, uint64 rows,
1381+
int64 rows_scanned,
12961382
const BufferUsage *bufusage,
12971383
const WalUsage *walusage,
12981384
const struct JitInstrumentation *jitusage,
@@ -1460,6 +1546,7 @@ pgss_store(const char *query, int64 queryId,
14601546
}
14611547
}
14621548
entry->counters.rows += rows;
1549+
entry->counters.rows_scanned += rows_scanned;
14631550
entry->counters.shared_blks_hit += bufusage->shared_blks_hit;
14641551
entry->counters.shared_blks_read += bufusage->shared_blks_read;
14651552
entry->counters.shared_blks_dirtied += bufusage->shared_blks_dirtied;
@@ -1581,7 +1668,8 @@ pg_stat_statements_reset(PG_FUNCTION_ARGS)
15811668
#define PG_STAT_STATEMENTS_COLS_V1_11 49
15821669
#define PG_STAT_STATEMENTS_COLS_V1_12 52
15831670
#define PG_STAT_STATEMENTS_COLS_V1_13 54
1584-
#define PG_STAT_STATEMENTS_COLS 54 /* maximum of above */
1671+
#define PG_STAT_STATEMENTS_COLS_V1_14 55
1672+
#define PG_STAT_STATEMENTS_COLS 55 /* maximum of above */
15851673

15861674
/*
15871675
* Retrieve statement statistics.
@@ -1593,6 +1681,16 @@ pg_stat_statements_reset(PG_FUNCTION_ARGS)
15931681
* expected API version is identified by embedding it in the C name of the
15941682
* function. Unfortunately we weren't bright enough to do that for 1.1.
15951683
*/
1684+
Datum
1685+
pg_stat_statements_1_14(PG_FUNCTION_ARGS)
1686+
{
1687+
bool showtext = PG_GETARG_BOOL(0);
1688+
1689+
pg_stat_statements_internal(fcinfo, PGSS_V1_14, showtext);
1690+
1691+
return (Datum) 0;
1692+
}
1693+
15961694
Datum
15971695
pg_stat_statements_1_13(PG_FUNCTION_ARGS)
15981696
{
@@ -1765,6 +1863,10 @@ pg_stat_statements_internal(FunctionCallInfo fcinfo,
17651863
if (api_version != PGSS_V1_13)
17661864
elog(ERROR, "incorrect number of output arguments");
17671865
break;
1866+
case PG_STAT_STATEMENTS_COLS_V1_14:
1867+
if (api_version != PGSS_V1_14)
1868+
elog(ERROR, "incorrect number of output arguments");
1869+
break;
17681870
default:
17691871
elog(ERROR, "incorrect number of output arguments");
17701872
}
@@ -1948,6 +2050,8 @@ pg_stat_statements_internal(FunctionCallInfo fcinfo,
19482050
}
19492051
}
19502052
values[i++] = Int64GetDatumFast(tmp.rows);
2053+
if (api_version >= PGSS_V1_14)
2054+
values[i++] = Int64GetDatumFast(tmp.rows_scanned);
19512055
values[i++] = Int64GetDatumFast(tmp.shared_blks_hit);
19522056
values[i++] = Int64GetDatumFast(tmp.shared_blks_read);
19532057
if (api_version >= PGSS_V1_1)
@@ -2038,6 +2142,7 @@ pg_stat_statements_internal(FunctionCallInfo fcinfo,
20382142
api_version == PGSS_V1_11 ? PG_STAT_STATEMENTS_COLS_V1_11 :
20392143
api_version == PGSS_V1_12 ? PG_STAT_STATEMENTS_COLS_V1_12 :
20402144
api_version == PGSS_V1_13 ? PG_STAT_STATEMENTS_COLS_V1_13 :
2145+
api_version == PGSS_V1_14 ? PG_STAT_STATEMENTS_COLS_V1_14 :
20412146
-1 /* fail if you forget to update this assert */ ));
20422147

20432148
tuplestore_putvalues(rsinfo->setResult, rsinfo->setDesc, values, nulls);
Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1,5 +1,5 @@
11
# pg_stat_statements extension
22
comment = 'track planning and execution statistics of all SQL statements executed'
3-
default_version = '1.13'
3+
default_version = '1.14'
44
module_pathname = '$libdir/pg_stat_statements'
55
relocatable = true

0 commit comments

Comments
 (0)