pg_stat_statements

Learn how to enable and query the pg_stat_statements extension to identify slow queries, high-frequency queries, and total database load for PostgreSQL performance analysis.

5 min read · Last updated: March 2026 · Back to overview

Quick Answer

pg_stat_statements is a PostgreSQL extension that tracks execution statistics for every distinct query the database runs—total calls, cumulative and mean execution time, rows returned, and cache hit rates. It is the standard starting point for identifying slow or high-frequency queries in production.

Spin up a Postgres database in 20 seconds with Vela.

Try Vela Sandbox

The pg_stat_statements extension records query-level performance data across all databases on a PostgreSQL instance. Every time a query runs, its statistics are aggregated by a normalized query fingerprint (literals are replaced with parameters). This makes it easy to identify which query patterns consume the most time, even across millions of executions.

Enabling pg_stat_statements

The extension ships with PostgreSQL but must be explicitly loaded. Add it to postgresql.conf and create the extension:

-- In postgresql.conf (requires restart)
shared_preload_libraries = 'pg_stat_statements'

-- After restarting, create the extension in the target database
CREATE EXTENSION IF NOT EXISTS pg_stat_statements;

Verify it is active:

SELECT * FROM pg_extension WHERE extname = 'pg_stat_statements';

Finding the slowest queries by mean execution time

SELECT
  LEFT(query, 80)   AS query_snippet,
  calls,
  ROUND(mean_exec_time::numeric, 2) AS mean_ms,
  ROUND(total_exec_time::numeric, 2) AS total_ms,
  rows
FROM pg_stat_statements
ORDER BY mean_exec_time DESC
LIMIT 20;

This surfaces queries with the highest average latency—candidates for index improvements or query rewrites.

Finding queries that consume the most total time

SELECT
  LEFT(query, 80)   AS query_snippet,
  calls,
  ROUND(total_exec_time::numeric, 2) AS total_ms,
  ROUND(mean_exec_time::numeric, 2)  AS mean_ms,
  ROUND((total_exec_time / SUM(total_exec_time) OVER ()) * 100, 1) AS pct_total
FROM pg_stat_statements
ORDER BY total_exec_time DESC
LIMIT 20;

Total time highlights the biggest contributors to overall database load—even fast queries can dominate if called millions of times.

Identifying cache-unfriendly queries

SELECT
  LEFT(query, 80)     AS query_snippet,
  calls,
  shared_blks_read    AS disk_reads,
  shared_blks_hit     AS cache_hits,
  ROUND(
    shared_blks_hit::numeric /
    NULLIF(shared_blks_hit + shared_blks_read, 0) * 100, 1
  ) AS cache_hit_pct
FROM pg_stat_statements
WHERE shared_blks_read > 0
ORDER BY shared_blks_read DESC
LIMIT 20;

A low cache_hit_pct means the query is reading data from disk frequently—consider adding an index or increasing shared_buffers.

Resetting statistics

-- Reset statistics for all queries (superuser only)
SELECT pg_stat_statements_reset();

-- Reset statistics for a single query by queryid (PostgreSQL 14+)
SELECT pg_stat_statements_reset(userid, dbid, queryid);

Reset statistics after a schema change or tuning effort so you get a clean baseline for the next measurement period.

Practical tips

  • Set pg_stat_statements.track = all (in postgresql.conf) to also track queries inside stored procedures and functions, not just top-level statements.
  • The query column normalizes literal values—WHERE id = 1 and WHERE id = 2 appear as the same fingerprint. Use queryid for programmatic lookups.
  • Statistics persist across connections but reset on server restart unless pg_stat_statements.save = on (default: on).
  • Combine with EXPLAIN (ANALYZE, BUFFERS) on the top offending queries to pinpoint exactly where time is spent.

Continue in Performance: Back to tutorial overview.

Related in this section: EXPLAIN · EXPLAIN ANALYZE · Indexes and Index-Only Scans

Frequently Asked Questions

What is pg_stat_statements in PostgreSQL?

pg_stat_statements is a built-in PostgreSQL extension that tracks cumulative execution statistics for every distinct SQL query pattern—call count, total and mean execution time, rows processed, and buffer usage. It is the primary tool for identifying slow or high-load queries in production.

How do I enable pg_stat_statements?

Add pg_stat_statements to shared_preload_libraries in postgresql.conf and restart PostgreSQL. Then run CREATE EXTENSION pg_stat_statements in the database where you want to query the statistics.

How are query texts normalized in pg_stat_statements?

Literal values (numbers, strings, etc.) in queries are replaced with parameter placeholders ($1, $2, ...) so that queries differing only in their literal values are grouped under the same fingerprint. This means "SELECT * FROM t WHERE id = 1" and "SELECT * FROM t WHERE id = 99" appear as one entry.

How do I find which queries use the most total database time?

Query pg_stat_statements ordered by total_exec_time DESC. This surfaces the queries with the highest cumulative execution time, which are the best targets for optimization since improving them reduces overall database load the most.

When should I reset pg_stat_statements?

Reset statistics with pg_stat_statements_reset() after significant schema changes, after deploying optimizations, or at the start of a load test to get a clean baseline. Without resetting, old statistics from deprecated queries continue to appear in the results.