Loading...
Loading...
Optimize SQL query performance using EXPLAIN analysis, indexing strategies, and common anti-pattern fixes. Use this skill when the user needs to speed up slow queries, design indexes, fix N+1 problems, or optimize database performance — even if they say 'this query is slow', 'optimize our database', 'which indexes do we need', or 'our dashboard takes 30 seconds to load'.
npx skill4agent add asgard-ai-platform/skills data-sql-optimizationIRON LAW: Measure Before Optimizing
NEVER guess which query is slow or why. Use EXPLAIN (EXPLAIN ANALYZE in
PostgreSQL) to see the actual execution plan. The database's plan often
differs from what you expect — a query you think is efficient may do
a full table scan, and a complex-looking query may use an index perfectly.
Measure → identify bottleneck → fix → measure again.| Metric | What It Means | Red Flag |
|---|---|---|
| Seq Scan | Full table scan | On large tables (>100K rows) |
| Index Scan | Using an index | Expected for filtered queries |
| Nested Loop | Join method (row-by-row) | On large tables without index |
| Hash Join | Join method (hash table) | Normal for larger tables |
| Sort | Sorting results | Without index support on large sets |
| Actual Time | Milliseconds for this step | Compare to identify bottleneck |
| Rows | Actual rows processed vs estimated | Large mismatch = stale statistics |
| When to Index | Index Type | Example |
|---|---|---|
| WHERE clause column | B-Tree (default) | |
| JOIN column | B-Tree | |
| Composite filter | Composite index | |
| Text search | GIN / Full-text | |
| Range queries | B-Tree | Columns used with |
INDEX(status, date)INDEX(date, status)| Anti-Pattern | Problem | Fix |
|---|---|---|
| Reads all columns, prevents index-only scans | Select only needed columns |
| Subquery in WHERE | Re-executes for each row | Rewrite as JOIN or CTE |
| Prevents index use | Rewrite as UNION or separate queries |
| Function on indexed column | | |
| N+1 queries | 1 query for list + N queries for details | JOIN or batch query with |
| Missing pagination | Fetching all rows when only showing 20 | |
| Implicit type conversion | | Use correct type: |
| Strategy | How It Works | Best For |
|---|---|---|
| Range partition | Split by date range (monthly, yearly) | Time-series data, logs |
| Hash partition | Distribute by hash of a column | Even distribution, high-throughput |
| List partition | Split by specific values | Multi-tenant, status-based |
# Query Optimization: {Context}
## Slow Query
```sql
{the original slow query}
## Gotchas
- **Indexes have write cost**: Every INSERT/UPDATE must update all indexes. Over-indexing slows writes. Index what you query, not everything.
- **Statistics can be stale**: If EXPLAIN estimates are way off from actuals, run `ANALYZE` (PostgreSQL) or `ANALYZE TABLE` (MySQL) to update statistics.
- **Query cache hides problems**: A query may appear fast because it's cached. Test with cache cleared or cold start.
- **ORM-generated queries**: ORMs (Django, SQLAlchemy, ActiveRecord) generate SQL that may not be optimal. Always inspect the actual SQL for performance-critical paths.
- **Connection pooling**: Sometimes the bottleneck isn't the query but connection overhead. Use connection pooling (PgBouncer, ProxySQL) for high-concurrency applications.
## References
- For PostgreSQL-specific optimization, see `references/pg-optimization.md`
- For CTE vs temp table performance comparison, see `references/cte-vs-temp.md`