Best Practices — Query Optimization
Module 16 · Query Optimization · Reference Library
Home · Checklist · Common Mistakes · Lesson 07
Engineering standards for writing, reviewing, and deploying SQL at production scale.
-
Select only needed columns. Never SELECT * in production queries. Explicit projection enables covering indexes and reduces network transfer.
-
Keep indexed columns bare in predicates. No functions, casts, or arithmetic on the column side of comparisons. Transform the constant instead.
-
Use explicit JOIN syntax. Always JOIN ... ON. Comma joins without ON conditions create cartesian products silently.
-
Prefer NOT EXISTS over NOT IN. NULL-safe, typically compiles to anti-join, no three-valued logic traps.
-
Prefer EXISTS over IN for existence checks. Short-circuits on first match; clearer intent.
-
Use window functions for per-group calculations. Replace correlated subqueries computing ranks, running totals, or group aggregates.
-
Use keyset pagination. Replace OFFSET n with WHERE cursor_col > last_value ORDER BY cursor_col LIMIT n.
-
Parameterize queries. Use bind parameters ($1, ?, @param) to benefit from plan cache reuse.
-
State production data volume. Every performance claim in documentation and PRs should specify assumed row counts.
-
One change at a time during tuning. Add one index OR rewrite one predicate OR refresh statistics — never all three simultaneously.
Index Design
- Index for your actual query patterns, not hypothetical future queries.
- Composite index column order: equality columns first, then range/ORDER BY columns.
- Covering indexes for high-frequency queries selecting few columns from large tables.
- Partial indexes for queries targeting a subset of rows (e.g.,
WHERE status = 'active').
- Audit existing indexes before adding new ones — remove unused indexes identified via engine statistics.
- Create indexes online in production:
CONCURRENTLY (PG), ONLINE = ON (SQL Server).
- Monitor index bloat on high-write tables, especially before peak traffic events.
Execution Plan Review
- Prefer EXPLAIN ANALYZE over plan-only EXPLAIN for slow query diagnosis, since it reports actual row counts and timings alongside the estimates. Exception: EXPLAIN ANALYZE executes the query (including any writes), so for expensive or mutating statements, review the plan-only EXPLAIN first or run ANALYZE inside a transaction you intend to roll back.
- Read plans bottom-up — innermost nodes execute first.
- Compare estimated vs. actual row counts — gap > 10× means stale statistics.
- Identify the single most expensive node before proposing any fix.
- Check for SubPlan with loops > 1 — correlated subquery not rewritten.
- Check for hash spill (Batches > 1) — work_mem exhaustion.
- Review on production-representative data — dev datasets hide performance problems.
Statistics & Maintenance
- Run ANALYZE after bulk loads, schema changes, or major DELETE operations.
- Configure auto-analyze with appropriate thresholds for high-churn tables.
- Monitor plan regressions via Query Store (SQL Server) or pg_stat_statements (PostgreSQL).
- Schedule regular index bloat checks on high-write tables.
Application Layer
- Eliminate N+1 queries — batch related lookups into single set-based queries.
- Set statement timeouts — prevent runaway queries from exhausting connection pools.
- Use connection pooling — PgBouncer, RDS Proxy, or engine-native pooler.
- Bound result sets — always LIMIT or cursor; never unbounded SELECT to application.
- Cache expensive read queries at the application layer when appropriate (Redis, materialized views).
Code Review Standards
- Every query touching tables > 100K rows must include EXPLAIN ANALYZE in the PR.
- No
NOT IN against nullable columns — reject in review.
- No
SELECT * in production code paths.
- No large OFFSET without documented keyset alternative.
- Assumed production data volume stated in PR description.
← Back to Module Home