-
Identify the slowest queries by examining pg_stat_statements (PostgreSQL): SELECT query, calls, mean_exec_time, total_exec_time FROM pg_stat_statements ORDER BY mean_exec_time DESC LIMIT 20. For MySQL, enable and query the slow query log or performance_schema.events_statements_summary_by_digest.
-
Run EXPLAIN (ANALYZE, BUFFERS, FORMAT JSON) on each slow query in PostgreSQL, or EXPLAIN ANALYZE FORMAT=JSON in MySQL. Capture the full execution plan including actual row counts, loop iterations, and buffer usage.
-
Analyze the execution plan for these red flags:
- Sequential scans on tables with >10,000 rows (indicates missing index)
- Nested loop joins with high outer row counts (consider hash join or merge join)
- Sort operations without index support (adding a covering index eliminates the sort)
- High
rows_removed_by_filter relative to rows (predicate not selective enough)
- Bitmap heap scans with high recheck rate (index selectivity too low)
-
Check buffer cache performance: SELECT heap_blks_read, heap_blks_hit, heap_blks_hit::float / (heap_blks_hit + heap_blks_read) AS cache_hit_ratio FROM pg_statio_user_tables WHERE relname = 'table_name'. A ratio below 0.95 suggests the working set exceeds available shared_buffers.
-
Evaluate index usage with SELECT indexrelname, idx_scan, idx_tup_read, idx_tup_fetch FROM pg_stat_user_indexes WHERE schemaname = 'public' ORDER BY idx_scan ASC. Indexes with zero scans are unused and waste write performance.
-
Check for table bloat using SELECT relname, n_live_tup, n_dead_tup, n_dead_tup::float / GREATEST(n_live_tup, 1) AS dead_ratio FROM pg_stat_user_tables WHERE n_dead_tup > 1000 ORDER BY dead_ratio DESC. A dead tuple ratio above 0.2 indicates the table needs VACUUM.
-
For each identified issue, generate a specific recommendation: CREATE INDEX statement with the exact columns, query rewrite suggestions, or configuration parameter adjustments.
-
Estimate the performance impact of each recommendation by comparing the EXPLAIN plan before and after applying the change on a staging database or by analyzing the expected row reduction from new indexes.
-
Prioritize recommendations by impact-to-effort ratio: index additions (high impact, low effort) before query rewrites (medium impact, medium effort) before schema changes (high impact, high effort).
-
Generate a performance analysis report with before/after execution plans, estimated improvements, and implementation priority ranking.