Catch plan regressions by queryid (pg_stat_statements snapshot comparison)
PostgreSQLPerformanceCommand
-- 1) Take a reference snapshot (before deploy/upgrade/ANALYZE)
CREATE TABLE IF NOT EXISTS pgss_snapshot AS
SELECT now() AS snap_time, userid, dbid, queryid, calls, total_exec_time,
shared_blks_hit, shared_blks_read, left(query, 200) AS query
FROM pg_stat_statements WITH NO DATA;
INSERT INTO pgss_snapshot
SELECT now(), userid, dbid, queryid, calls, total_exec_time,
shared_blks_hit, shared_blks_read, left(query, 200)
FROM pg_stat_statements;
-- 2) Later: queries whose time per call grew the most since the snapshot
WITH base AS (
SELECT DISTINCT ON (userid, dbid, queryid) *
FROM pgss_snapshot
ORDER BY userid, dbid, queryid, snap_time DESC
)
SELECT s.queryid,
s.calls - b.calls AS new_calls,
round((b.total_exec_time / b.calls)::numeric, 2) AS old_mean_ms,
round(((s.total_exec_time - b.total_exec_time) / (s.calls - b.calls))::numeric, 2) AS new_mean_ms,
round((b.shared_blks_hit + b.shared_blks_read)::numeric / b.calls, 0) AS old_blks_per_call,
round((s.shared_blks_hit + s.shared_blks_read - b.shared_blks_hit - b.shared_blks_read)::numeric
/ (s.calls - b.calls), 0) AS new_blks_per_call,
left(regexp_replace(s.query, '\s+', ' ', 'g'), 100) AS query
FROM pg_stat_statements s
JOIN base b USING (userid, dbid, queryid)
WHERE s.calls - b.calls >= 10
AND b.calls > 0
ORDER BY ((s.total_exec_time - b.total_exec_time) / (s.calls - b.calls))
/ NULLIF(b.total_exec_time / b.calls, 0) DESC NULLS LAST
LIMIT 20;
-- 3) Is the suspicious queryid running right now?
SELECT pid, state, now() - query_start AS runtime, left(query, 100) AS query
FROM pg_stat_activity
WHERE query_id = -4094682686355101727;What it does
Saves pg_stat_statements' cumulative counters to a table and compares them with the later state per queryid; shows the mean time and buffers per call of calls made after the snapshot next to the old values.
When to use it
To find the query whose plan changed after a deploy, major upgrade, statistics refresh or parameter change ('it was fast yesterday, slow today').
Caution
If pg_stat_statements_reset() runs between the snapshot and the comparison, or entries are evicted for lack of space (dealloc), the deltas are meaningless; check pg_stat_statements_info.stats_reset. An increase in buffers per call is a more reliable sign of a plan change than an increase in time, because it is independent of caching. To confirm the plan, EXPLAIN the query with real parameters or use auto_explain; PostgreSQL has no built-in plan pinning (the pg_hint_plan extension is required). Drop the snapshot table when done.
Related
- Find the most expensive queries (pg_stat_statements: total and mean time)
- Find queries driving disk I/O, temp files and WAL (pg_stat_statements)
- Reset pg_stat_statements statistics (all or a single query)
- Log execution plans of slow queries with auto_explain
- Read a plan in depth with EXPLAIN (ANALYZE, BUFFERS, SETTINGS, WAL)
- Check parallel query capacity and current worker usage