Debug Slow Queries
Quick Start
Goal: go from "slow" to a verified fix with minimal risk.
When to use this skill
- p95 latency spikes, timeouts, or sudden cpu growth tied to database load
- a deploy introduces query regression
- tables grow and plans flip (index → seq scan, join strategy changes)
inputs to collect (before changing anything)
- database engine + version
- query text + typical parameters (not placeholders only)
- runtime evidence: p50/p95, rows returned, timeouts, error logs
- table sizes / row counts (approx)
- relevant indexes + constraints
- environment: prod vs staging, read replica vs primary
Workflow (default)
- reproduce or capture
- reproduce in staging with production-like data, or capture the exact query + params from logs
- baseline
- record current p50/p95 and the explain plan
- read the plan
- identify where time is spent (scan, join, sort, aggregate)
- pick the cheapest safe fix
- rewrite query → add/adjust index → update stats → change schema
- verify
- re-run explain, compare timing, validate correctness
- roll out safely
- ship behind a flag if needed, monitor, keep rollback path
Explain plan patterns (what to look for)
scans
- sequential scan on large table
- likely missing selective predicate index, or predicate is not sargable
- index scan returning many rows
- index exists but selectivity is poor; may need different leading columns or a partial index
joins
- nested loop with huge inner iterations
- inner side needs an index on join key, or join order needs improvement
- hash join spilling
- work_mem/memory pressure or too many rows; reduce join input with earlier filters
sorts / aggregates
- sort on large result
- add index to match order by, or paginate differently
- group by on many rows
- pre-filter, pre-aggregate, or add covering index for grouping columns
Engine-specific commands
PostgreSQL
get plan + timing:
EXPLAIN (ANALYZE, BUFFERS, VERBOSE) <query>;
check table stats freshness:
SELECT relname, n_live_tup, last_analyze, last_autoanalyze
FROM pg_stat_user_tables
ORDER BY n_live_tup DESC;
see indexes:
SELECT tablename, indexname, indexdef
FROM pg_indexes
WHERE schemaname = 'public' AND tablename = 'your_table';
safe index creation:
CREATE INDEX CONCURRENTLY IF NOT EXISTS idx_name ON your_table (col1, col2);
MySQL
get plan:
EXPLAIN <query>;
get runtime plan details (mysql 8):
EXPLAIN ANALYZE <query>;
see indexes:
SHOW INDEX FROM your_table;
SQLite
get plan:
EXPLAIN QUERY PLAN <query>;
Fix toolbox (ordered by typical safety)
1) query rewrites (often best first)
- limit early
- move filters into subqueries/ctes only if they reduce rows before joins
- avoid non-sargable predicates
- avoid wrapping indexed columns in functions in where clauses
- replace
select *
- reduce io and sort payload
- avoid
offset pagination at scale
- use keyset pagination when possible
2) index changes
Use this checklist:
- does the where clause filter on a selective column?
- does the join predicate have an index on the inner side?
- does order by match an index prefix?
- is a composite index needed (leading columns matter)?
- can a partial index reduce size (postgres)?
3) statistics / maintenance
- analyze / vacuum (postgres) or optimize/analyze table (mysql) can fix bad estimates
- confirm cardinality estimates vs actual rows in explain analyze
4) schema-level changes (highest cost)
- denormalize only after proving query/index fixes are insufficient
- consider materialized views / summary tables for heavy aggregates
Output format (copy/paste)
## slow query report
### symptom
- endpoint/job:
- p50/p95:
- query:
### evidence
- explain plan notes:
- row counts:
- indexes involved:
### root cause
- primary bottleneck:
- why it happens:
### fix
- change:
- risk:
- rollback:
### verification
- before:
- after:
- correctness checks:
1---2name: debug-slow-queries3description: Diagnoses and fixes slow database queries using explain plans, statistics, and targeted indexes or rewrites. Use when an endpoint is slow, a query regresses, cpu spikes, or timeouts appear.4---56# Debug Slow Queries78## Quick Start910Goal: go from "slow" to a verified fix with minimal risk.1112## When to use this skill1314- p95 latency spikes, timeouts, or sudden cpu growth tied to database load15- a deploy introduces query regression16- tables grow and plans flip (index → seq scan, join strategy changes)1718### inputs to collect (before changing anything)1920- database engine + version21- query text + typical parameters (not placeholders only)22- runtime evidence: p50/p95, rows returned, timeouts, error logs23- table sizes / row counts (approx)24- relevant indexes + constraints25- environment: prod vs staging, read replica vs primary2627## Workflow (default)28291. **reproduce or capture**30 - reproduce in staging with production-like data, or capture the exact query + params from logs312. **baseline**32 - record current p50/p95 and the explain plan333. **read the plan**34 - identify where time is spent (scan, join, sort, aggregate)354. **pick the cheapest safe fix**36 - rewrite query → add/adjust index → update stats → change schema375. **verify**38 - re-run explain, compare timing, validate correctness396. **roll out safely**40 - ship behind a flag if needed, monitor, keep rollback path4142## Explain plan patterns (what to look for)4344### scans4546- **sequential scan on large table**47 - likely missing selective predicate index, or predicate is not sargable48- **index scan returning many rows**49 - index exists but selectivity is poor; may need different leading columns or a partial index5051### joins5253- **nested loop with huge inner iterations**54 - inner side needs an index on join key, or join order needs improvement55- **hash join spilling**56 - work_mem/memory pressure or too many rows; reduce join input with earlier filters5758### sorts / aggregates5960- **sort on large result**61 - add index to match order by, or paginate differently62- **group by on many rows**63 - pre-filter, pre-aggregate, or add covering index for grouping columns6465## Engine-specific commands6667### PostgreSQL6869**get plan + timing:**7071```sql72EXPLAIN (ANALYZE, BUFFERS, VERBOSE) <query>;73```7475**check table stats freshness:**7677```sql78SELECT relname, n_live_tup, last_analyze, last_autoanalyze79FROM pg_stat_user_tables80ORDER BY n_live_tup DESC;81```8283**see indexes:**8485```sql86SELECT tablename, indexname, indexdef87FROM pg_indexes88WHERE schemaname = 'public' AND tablename = 'your_table';89```9091**safe index creation:**9293```sql94CREATE INDEX CONCURRENTLY IF NOT EXISTS idx_name ON your_table (col1, col2);95```9697### MySQL9899**get plan:**100101```sql102EXPLAIN <query>;103```104105**get runtime plan details (mysql 8):**106107```sql108EXPLAIN ANALYZE <query>;109```110111**see indexes:**112113```sql114SHOW INDEX FROM your_table;115```116117### SQLite118119**get plan:**120121```sql122EXPLAIN QUERY PLAN <query>;123```124125## Fix toolbox (ordered by typical safety)126127### 1) query rewrites (often best first)128129- **limit early**130 - move filters into subqueries/ctes only if they reduce rows before joins131- **avoid non-sargable predicates**132 - avoid wrapping indexed columns in functions in where clauses133- **replace `select *`**134 - reduce io and sort payload135- **avoid `offset` pagination at scale**136 - use keyset pagination when possible137138### 2) index changes139140Use this checklist:141142- does the where clause filter on a selective column?143- does the join predicate have an index on the inner side?144- does order by match an index prefix?145- is a composite index needed (leading columns matter)?146- can a partial index reduce size (postgres)?147148### 3) statistics / maintenance149150- analyze / vacuum (postgres) or optimize/analyze table (mysql) can fix bad estimates151- confirm cardinality estimates vs actual rows in explain analyze152153### 4) schema-level changes (highest cost)154155- denormalize only after proving query/index fixes are insufficient156- consider materialized views / summary tables for heavy aggregates157158## Output format (copy/paste)159160```markdown161## slow query report162163### symptom164165- endpoint/job:166- p50/p95:167- query:168169### evidence170171- explain plan notes:172- row counts:173- indexes involved:174175### root cause176177- primary bottleneck:178- why it happens:179180### fix181182- change:183- risk:184- rollback:185186### verification187188- before:189- after:190- correctness checks:191```