Overview
Analyzes slow SQL queries using EXPLAIN / EXPLAIN ANALYZE, recommends and implements index strategies (B-tree, partial, composite, covering), detects and fixes N+1 problems, optimizes JOINs, subqueries vs CTEs vs window functions, and provides concrete before/after query rewrites with performance numbers.
When to Use This Skill
- A query is slow in production or during development.
- Building new reports or features that query the database.
- Reviewing ORM-generated queries that are causing problems.
- Preparing for a database migration or scaling event.
Prerequisites
- Access to the database (read-only for analysis, or a staging copy for testing).
- The slow query (or the code that generates it).
EXPLAIN support (all major relational DBs).
- For production changes: migration tool (Alembic, Flyway, Prisma, etc.).
Steps
Capture the slow query:
- From logs, APM (pg_stat_statements, slow query log), or application tracing.
- Get the full query text with parameters.
Run EXPLAIN ANALYZE (or EXPLAIN on SQLite):
EXPLAIN ANALYZE SELECT ...;
- Look for: Seq Scan on large tables, high cost, many rows removed by filter, Nested Loop with high row counts.
Index strategy:
- B-tree for equality and range.
- Composite indexes in column order of the WHERE/JOIN/ORDER BY.
- Partial indexes for common filters (e.g.,
WHERE status = 'active').
- Covering indexes (INCLUDE in Postgres) to avoid table lookups.
Fix N+1:
- Use JOINs or
IN subquery / lateral join instead of looping in application code.
- For ORMs: use
select_related / prefetch_related (Django), includes / eager_load (ActiveRecord), or write a single query.
Rewrite patterns:
- Subquery → JOIN or CTE.
- Correlated subquery → window function or lateral.
SELECT * → only needed columns.
ORDER BY without LIMIT on large result sets → paginate or use cursor.
Test the change:
- Run EXPLAIN ANALYZE before and after.
- Run the query with realistic data volume.
- Check that the new index does not slow down writes unacceptably (measure insert/update time).
Output:
- Original query + EXPLAIN.
- Diagnosis.
- Optimized query + new EXPLAIN.
- Index creation statement (with
CONCURRENTLY for Postgres production).
- Application code change (if N+1).
- Monitoring recommendation (add to pg_stat_statements watch list).
Examples
A classic slow "get recent orders with customer and items" N+1 query, its EXPLAIN, the diagnosis, the rewritten JOIN + covering index version, and the before/after timing (e.g., 2.3s → 12ms) are included, along with similar examples for MySQL and SQLite.
Edge Cases & Error Handling
- Write-heavy tables: Partial or filtered indexes to reduce write overhead.
- Very large tables: Partitioning + query that can use partition pruning.
- Parameter sniffing (SQL Server) or plan cache issues: use
RECOMPILE or plan guides when needed.
- ORM limitations: Sometimes the best fix is a raw SQL query or a database view.
Verification
- New EXPLAIN shows index usage (Index Scan / Index Only Scan instead of Seq Scan).
- Query time improves by at least 5-10x on realistic data.
- No increase in write latency after adding the index (measured on staging).
- Application tests still pass (the optimized query returns identical results).
- Success: The slow path is fixed, and the query plan is stable and efficient.
References
1---2name: sql-query-optimizer3description: Analyzes and optimizes slow SQL queries for PostgreSQL, MySQL, or SQLite. Use when queries are slow, or when building efficient queries from scratch.4license: Apache-2.05---67## Overview89Analyzes slow SQL queries using EXPLAIN / EXPLAIN ANALYZE, recommends and implements index strategies (B-tree, partial, composite, covering), detects and fixes N+1 problems, optimizes JOINs, subqueries vs CTEs vs window functions, and provides concrete before/after query rewrites with performance numbers.1011## When to Use This Skill1213- A query is slow in production or during development.14- Building new reports or features that query the database.15- Reviewing ORM-generated queries that are causing problems.16- Preparing for a database migration or scaling event.1718## Prerequisites1920- Access to the database (read-only for analysis, or a staging copy for testing).21- The slow query (or the code that generates it).22- `EXPLAIN` support (all major relational DBs).23- For production changes: migration tool (Alembic, Flyway, Prisma, etc.).2425## Steps26271. **Capture the slow query**:28 - From logs, APM (pg_stat_statements, slow query log), or application tracing.29 - Get the full query text with parameters.30312. **Run EXPLAIN ANALYZE** (or EXPLAIN on SQLite):32 ```sql33 EXPLAIN ANALYZE SELECT ...;34 ```35 - Look for: Seq Scan on large tables, high cost, many rows removed by filter, Nested Loop with high row counts.36373. **Index strategy**:38 - B-tree for equality and range.39 - Composite indexes in column order of the WHERE/JOIN/ORDER BY.40 - Partial indexes for common filters (e.g., `WHERE status = 'active'`).41 - Covering indexes (INCLUDE in Postgres) to avoid table lookups.42434. **Fix N+1**:44 - Use JOINs or `IN` subquery / lateral join instead of looping in application code.45 - For ORMs: use `select_related` / `prefetch_related` (Django), `includes` / `eager_load` (ActiveRecord), or write a single query.46475. **Rewrite patterns**:48 - Subquery → JOIN or CTE.49 - Correlated subquery → window function or lateral.50 - `SELECT *` → only needed columns.51 - `ORDER BY` without LIMIT on large result sets → paginate or use cursor.52536. **Test the change**:54 - Run EXPLAIN ANALYZE before and after.55 - Run the query with realistic data volume.56 - Check that the new index does not slow down writes unacceptably (measure insert/update time).57587. **Output**:59 - Original query + EXPLAIN.60 - Diagnosis.61 - Optimized query + new EXPLAIN.62 - Index creation statement (with `CONCURRENTLY` for Postgres production).63 - Application code change (if N+1).64 - Monitoring recommendation (add to pg_stat_statements watch list).6566## Examples6768A classic slow "get recent orders with customer and items" N+1 query, its EXPLAIN, the diagnosis, the rewritten JOIN + covering index version, and the before/after timing (e.g., 2.3s → 12ms) are included, along with similar examples for MySQL and SQLite.6970## Edge Cases & Error Handling7172- **Write-heavy tables**: Partial or filtered indexes to reduce write overhead.73- **Very large tables**: Partitioning + query that can use partition pruning.74- **Parameter sniffing** (SQL Server) or plan cache issues: use `RECOMPILE` or plan guides when needed.75- **ORM limitations**: Sometimes the best fix is a raw SQL query or a database view.7677## Verification78791. New EXPLAIN shows index usage (Index Scan / Index Only Scan instead of Seq Scan).802. Query time improves by at least 5-10x on realistic data.813. No increase in write latency after adding the index (measured on staging).824. Application tests still pass (the optimized query returns identical results).835. Success: The slow path is fixed, and the query plan is stable and efficient.8485## References8687- [PostgreSQL EXPLAIN](https://www.postgresql.org/docs/current/using-explain.html)88- [Use The Index, Luke](https://use-the-index-luke.com/)89- [MySQL EXPLAIN](https://dev.mysql.com/doc/refman/8.0/en/explain.html)90- [pg_stat_statements](https://www.postgresql.org/docs/current/pgstatstatements.html)