How to Diagnose Slow Queries in PostgreSQL

Galaxy Glossary

How do I find and fix slow queries in PostgreSQL?

Identify, measure, and optimize SQL statements that consume excessive time or resources in PostgreSQL.

Sign up for the latest in SQL knowledge from the Galaxy Team!
Welcome to the Galaxy, Guardian!
You'll be receiving a confirmation email

Follow us on twitter :)
Oops! Something went wrong while submitting the form.

Description

Table of Contents

Why is my PostgreSQL query slow?

Queries become slow when they scan large tables, miss indexes, fetch more rows than needed, or wait on locks. Measuring execution plans pinpoints the exact cause.

How do I measure a query’s cost with EXPLAIN ANALYZE?

Run EXPLAIN ANALYZE before your statement. PostgreSQL returns the execution plan with actual timings, row counts, and I/O statistics, showing where the time is spent.

Step-by-step EXPLAIN ANALYZE

1️⃣ Prefix the statement with EXPLAIN (ANALYZE, BUFFERS).
2️⃣ Inspect high Actual Total Time nodes.
3️⃣ Create or adjust indexes on filter and join columns.
4️⃣ Re-run to verify improvement.

How can I list the worst-offending queries globally?

Enable the pg_stat_statements extension. It records execution counts, total time, and I/O per normalized query, letting you rank statements by cost.

Enabling pg_stat_statements

1️⃣ Add shared_preload_libraries = 'pg_stat_statements' in postgresql.conf.
2️⃣ Restart PostgreSQL.
3️⃣ CREATE EXTENSION pg_stat_statements;

Querying the statistics view

Use SELECT on pg_stat_statements ordered by total_exec_time or mean_exec_time to surface slow SQL.

How do I auto-log slow plans?

Load the auto_explain module and set auto_explain.log_min_duration (ms). PostgreSQL logs plans for statements exceeding that threshold, even in production.

How do I rewrite a slow ecommerce query?

Replace SELECT * with explicit columns, add LIMIT, and push filters early. Combine EXISTS instead of IN for large sub-queries. Always create composite indexes matching your WHERE and JOIN predicates.

Refactor example

Original: SELECT * FROM Orders WHERE customer_id IN (SELECT id FROM Customers WHERE created_at < now()-interval '1 year');
Optimized: SELECT o.id, o.order_date, o.total_amount FROM Orders o JOIN Customers c ON c.id = o.customer_id WHERE c.created_at < now()-interval '1 year';

Best practices to avoid slow queries?

Analyze tables regularly, keep statistics up-to-date, index selectively, retrieve only needed rows, and monitor pg_stat_statements weekly.

Common Mistakes

Missing composite indexes: Indexing only one column of a multi-column predicate forces sequential scans. Create indexes that match the full filter.

Selecting unnecessary columns: Using SELECT * increases I/O and memory usage. Specify only required fields.

FAQs

Does EXPLAIN change data?

No. EXPLAIN reads metadata and never modifies rows.

Is pg_stat_statements safe in production?

Yes. It adds minimal overhead (<2%) and is widely used in live systems.

What threshold should I set for auto_explain.log_min_duration?

Start at 500 ms. Lower it gradually after you fix the worst offenders.

Why How to Diagnose Slow Queries in PostgreSQL is important

How to Diagnose Slow Queries in PostgreSQL Example Usage


-- Find slow ecommerce queries
SELECT query,
       total_exec_time/1000 AS seconds,
       calls,
       (total_exec_time/calls) AS avg_ms
FROM pg_stat_statements
WHERE dbid = (SELECT oid FROM pg_database WHERE datname = current_database())
ORDER BY total_exec_time DESC
LIMIT 5;

How to Diagnose Slow Queries in PostgreSQL Syntax


EXPLAIN [ ( option [, ...] ) ] statement
  option :=
    ANALYZE [ boolean ]
  | VERBOSE [ boolean ]
  | COSTS [ boolean ]
  | BUFFERS [ boolean ]
  | FORMAT { TEXT | JSON | YAML }

CREATE EXTENSION pg_stat_statements;

SELECT *
FROM pg_stat_statements
ORDER BY total_exec_time DESC
LIMIT 10;

-- auto_explain config
shared_preload_libraries = 'auto_explain'
auto_explain.log_min_duration = 500  -- ms

auto_explain.log_analyze = on

Common Mistakes

Frequently Asked Questions (FAQs)

Does EXPLAIN ANALYZE lock tables?

No. It runs the query normally and collects timing data without adding locks beyond what the query itself requires.

Can I reset pg_stat_statements?

Yes. Execute SELECT pg_stat_statements_reset(); to clear all statistics.

What permissions are needed to view pg_stat_statements?

You need the pg_read_all_stats role or superuser rights to query the view.

Want to learn about other SQL terms?

Trusted by top engineers on high-velocity teams
Aryeo Logo
Assort Health
Curri
Rubie Logo
Bauhealth Logo
Truvideo Logo
Welcome to the Galaxy, Guardian!
You'll be receiving a confirmation email

Follow us on twitter :)
Oops! Something went wrong while submitting the form.