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.05compatibility: Claude Code, Codex, Gemini CLI6metadata:7 author: ai-skills8 version: "1.0"9 category: data-analytics10 tags: sql, postgres, mysql, optimization, explain, indexes, performance11---1213## Overview1415Analyzes 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.1617## When to Use This Skill1819- A query is slow in production or during development.20- Building new reports or features that query the database.21- Reviewing ORM-generated queries that are causing problems.22- Preparing for a database migration or scaling event.2324## Prerequisites2526- Access to the database (read-only for analysis, or a staging copy for testing).27- The slow query (or the code that generates it).28- `EXPLAIN` support (all major relational DBs).29- For production changes: migration tool (Alembic, Flyway, Prisma, etc.).3031## Steps32331. **Capture the slow query**:34 - From logs, APM (pg_stat_statements, slow query log), or application tracing.35 - Get the full query text with parameters.36372. **Run EXPLAIN ANALYZE** (or EXPLAIN on SQLite):38 ```sql39 EXPLAIN ANALYZE SELECT ...;40 ```41 - Look for: Seq Scan on large tables, high cost, many rows removed by filter, Nested Loop with high row counts.42433. **Index strategy**:44 - B-tree for equality and range.45 - Composite indexes in column order of the WHERE/JOIN/ORDER BY.46 - Partial indexes for common filters (e.g., `WHERE status = 'active'`).47 - Covering indexes (INCLUDE in Postgres) to avoid table lookups.48494. **Fix N+1**:50 - Use JOINs or `IN` subquery / lateral join instead of looping in application code.51 - For ORMs: use `select_related` / `prefetch_related` (Django), `includes` / `eager_load` (ActiveRecord), or write a single query.52535. **Rewrite patterns**:54 - Subquery → JOIN or CTE.55 - Correlated subquery → window function or lateral.56 - `SELECT *` → only needed columns.57 - `ORDER BY` without LIMIT on large result sets → paginate or use cursor.58596. **Test the change**:60 - Run EXPLAIN ANALYZE before and after.61 - Run the query with realistic data volume.62 - Check that the new index does not slow down writes unacceptably (measure insert/update time).63647. **Output**:65 - Original query + EXPLAIN.66 - Diagnosis.67 - Optimized query + new EXPLAIN.68 - Index creation statement (with `CONCURRENTLY` for Postgres production).69 - Application code change (if N+1).70 - Monitoring recommendation (add to pg_stat_statements watch list).7172## Examples7374A 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.7576## Edge Cases & Error Handling7778- **Write-heavy tables**: Partial or filtered indexes to reduce write overhead.79- **Very large tables**: Partitioning + query that can use partition pruning.80- **Parameter sniffing** (SQL Server) or plan cache issues: use `RECOMPILE` or plan guides when needed.81- **ORM limitations**: Sometimes the best fix is a raw SQL query or a database view.8283## Verification84851. New EXPLAIN shows index usage (Index Scan / Index Only Scan instead of Seq Scan).862. Query time improves by at least 5-10x on realistic data.873. No increase in write latency after adding the index (measured on staging).884. Application tests still pass (the optimized query returns identical results).895. Success: The slow path is fixed, and the query plan is stable and efficient.9091## References9293- [PostgreSQL EXPLAIN](https://www.postgresql.org/docs/current/using-explain.html)94- [Use The Index, Luke](https://use-the-index-luke.com/)95- [MySQL EXPLAIN](https://dev.mysql.com/doc/refman/8.0/en/explain.html)96- [pg_stat_statements](https://www.postgresql.org/docs/current/pgstatstatements.html)