How to enable pg_stat_statements in Postgres
pg_stat_statements is the Postgres extension that tracks execution stats for
every (normalized) query your database runs — call counts, total and mean
execution time, rows. It's the foundation of almost every Postgres performance
tool, including pgblame. Here's how to turn it on.
Self-hosted Postgres
1. Preload the library
pg_stat_statements must be loaded at server start, so add it to
shared_preload_libraries in postgresql.conf:
# postgresql.conf
shared_preload_libraries = 'pg_stat_statements'
If you already have other libraries listed, append it comma-separated:
'pg_cron,pg_stat_statements'.
2. Restart Postgres
shared_preload_libraries is only read at startup, so a reload isn't enough —
restart the server:
sudo systemctl restart postgresql
3. Create the extension
Connect as a superuser and register it in the database you want to monitor:
CREATE EXTENSION IF NOT EXISTS pg_stat_statements;
4. Verify it's collecting
SELECT count(*) FROM pg_stat_statements;
-- a growing number of rows = it's working
Managed Postgres (Supabase, Neon, RDS)
Managed providers already preload the library — you don't edit
postgresql.conf or restart. You only run the CREATE EXTENSION step (and on
some platforms, toggle it in a UI):
- Supabase — Database → Extensions, search
pg_stat_statements, toggle on. Note it lives in theextensionsschema. See Supabase query performance. - Neon — run
CREATE EXTENSION IF NOT EXISTS pg_stat_statements;in the SQL editor; it's preloaded on every branch. - Amazon RDS / Aurora — it's in the default parameter group's
shared_preload_libraries; if you use a custom parameter group, add it there, thenCREATE EXTENSION.
Reading the slowest queries
Once it's collecting, your top queries by cumulative cost:
SELECT
query,
calls,
round(mean_exec_time::numeric, 1) AS avg_ms,
round(total_exec_time::numeric, 0) AS total_ms
FROM pg_stat_statements
ORDER BY total_exec_time DESC
LIMIT 20;
pg_stat_statements only knows totals since the last reset. Run that query
today and you see what's slow now — but not whether a query got slower last
Tuesday, or which deploy did it. The view has no time dimension. To answer
"which deploy slowed this?", something has to snapshot these numbers on a
schedule and line them up against your releases.
That's the job pgblame automates: it reads pg_stat_statements every 60
seconds and correlates the deltas with your Vercel/Railway/GitHub Actions
deploys, so a regression points straight at the release that caused it. If
you'd rather not pay $149/mo for that, it's the
indie-priced alternative to pganalyze.
See which deploy slowed your Postgres — free, no card.
Start free — no cardFAQ
- Does enabling pg_stat_statements require a restart?
- Only on self-hosted Postgres, and only the first time — because shared_preload_libraries is read at startup. On managed Postgres (Supabase, Neon, RDS) the library is already preloaded, so CREATE EXTENSION alone is enough and needs no restart.
- Is pg_stat_statements safe to run in production?
- Yes. It's the standard query-stats extension, used everywhere. Overhead is low and bounded by pg_stat_statements.max (default 5000 tracked statements). It records query shape and timing, not row data.
- Why is pg_stat_statements empty after enabling it?
- Either the library wasn't preloaded (so the extension is installed but collecting nothing — check shared_preload_libraries and restart), or no queries have run since the last reset. It only tracks normalized statements executed after it loaded.
- Does it track every query?
- It tracks normalized statements (literals stripped) up to pg_stat_statements.max distinct entries. Beyond that, the least-used entries are evicted, so very high-cardinality workloads may not capture everything.