PostgreSQL Slow Queries & High CPU — Performance Guide
Comprehensive guide for diagnosing slow PostgreSQL queries, missing indexes, N+1 query patterns, and connection pool bottlenecks.
Database bottlenecks are responsible for over 80% of application latency spikes. This guide outlines how to trace slow PostgreSQL queries and optimize query execution in production.
⚡ 3-Step Diagnostic Workflow
1. Identify Slow Queries with pg_stat_statements
Run the following query to surface top time-consuming database operations:
SELECT
round(total_exec_time::numeric, 2) AS total_ms,
calls,
round(mean_exec_time::numeric, 2) AS avg_ms,
query
FROM pg_stat_statements
ORDER BY total_exec_time DESC
LIMIT 10;2. Analyze Query Plans with EXPLAIN ANALYZE
Prefix any slow query with EXPLAIN (ANALYZE, BUFFERS) to inspect how PostgreSQL executes it:
EXPLAIN (ANALYZE, BUFFERS)
SELECT * FROM orders WHERE user_id = 42 AND status = 'pending';Key Metrics to Watch:
Seq Scan: Indicates a missing index on queried fields (e.g.,user_idorstatus).External sort Disk: Indicateswork_memis insufficient for in-memory sorting.
3. Add Composite Indexes for Filtered & Sorted Fields
-- Create composite index covering filter criteria
CREATE INDEX CONCURRENTLY idx_orders_user_status ON orders (user_id, status);Tip
Always execute CREATE INDEX CONCURRENTLY in production environments to avoid blocking table writes during index creation.
🚫 Preventing N+1 Query Patterns in ORMs
ORMs like Prisma, Drizzle, and TypeORM can easily generate silent N+1 query loops if relationships are loaded sequentially.
❌ Problematic N+1 Loop
// ❌ FAILS: Executes 1 initial query + N sub-queries (100 users = 101 queries)
const users = await db.user.findMany();
for (const user of users) {
const posts = await db.post.findMany({ where: { userId: user.id } });
}✅ Optimized Single Query
// ✅ FIXED: Executes 1 batch query with SQL JOIN
const usersWithPosts = await db.user.findMany({
include: { posts: true },
});📊 Summary Checklist
| Performance Area | Symptom | Action Item |
|---|---|---|
| Missing Index | High CPU & Seq Scan in EXPLAIN |
Add targeted single or composite B-Tree indexes |
| N+1 ORM Loops | High query count per request | Batch relationships with .findMany({ include }) |
| Memory Spills | External sort Disk |
Tune work_mem in postgresql.conf |
Facing persistent database slowdowns or locking issues? Get a performance audit or diagnose your issue.
Frequently asked questions
Enable pg_stat_statements or configure log_min_duration_statement = 200 in postgresql.conf.
It executes the query and returns actual execution times, node costs, index usage, and scan types (Seq Scan vs Index Scan).
ORMs often generate N+1 query loops or select all columns without indexes, overwhelming connection pools and CPU.
Experiencing a similar issue?
Describe it and get an automated scoping estimate in seconds, with the option to book a free 30-minute diagnostic call.