Optimizing SQL Query Performance

1. Analyzing Query Execution Plans

OperatorMeaning
Seq Scan / Table ScanRead every row — bad on large tables
Index ScanUse index to find rows, fetch from heap
Index Only ScanAll needed cols in index
Bitmap Index ScanCombine multiple indexes
Nested Loop / Hash / Merge JoinJoin algorithms
Sort / Hash AggregateGrouping/ordering work
Gather (Parallel)Parallel workers combine results

Example: EXPLAIN ANALYZE

EXPLAIN (ANALYZE, BUFFERS, VERBOSE, FORMAT TEXT)
SELECT c.name, SUM(o.total)
FROM customers c JOIN orders o ON o.customer_id = c.id
WHERE o.placed_at >= NOW() - INTERVAL '30 days'
GROUP BY c.name;

2. Identifying Performance Bottlenecks

SymptomLikely Cause
Seq Scan on big tableMissing index / non-sargable predicate
Rows estimate vs actual mismatchStale statistics
High Buffers: readCache miss → I/O bound
External sortwork_mem too small
Wide rows / TOASTDetoasting large values

3. Optimizing WHERE Clause Predicates

Sargable (good)Non-sargable (bad)
col = 'x'LOWER(col) = 'x'
created_at >= '2026-05-01'YEAR(created_at) = 2026
col LIKE 'abc%'col LIKE '%abc%'
id = 42CAST(id AS TEXT) = '42'
Note: Functional indexes can save non-sargable patterns: CREATE INDEX ON users(LOWER(email));

4. Using Index Hints and Query Hints

DBHint
MySQLUSE INDEX(idx), FORCE INDEX, IGNORE INDEX
Oracle/*+ INDEX(t idx) */
SQL ServerWITH (INDEX(idx)), OPTION (HASH JOIN)
PostgresNo native hints; use pg_hint_plan extension or set planner flags
Warning: Hints lock plans against future improvements. Use as a last resort after stats and indexes are correct.

5. Rewriting Queries for Performance

PatternRewrite
SELECT *List needed cols → enables index-only scan
OR conditionsUNION ALL of single-predicate queries
NOT IN with NULLsNOT EXISTS
Correlated subqueryJOIN + GROUP BY or window
LIMIT + OFFSETKeyset pagination
COUNT(*) on huge tableApproximation: pg_class.reltuples, materialized counter

6. Optimizing Joins

TipDetail
Index FK columnsBoth sides of equi-join
Drive from selective sideFilter early
Avoid functions on join keysPrevents index use
Tune work_memEnables hash joins to stay in-memory
Bloom indexes / Bloom join (rare)Pre-filter probe side

7. Avoiding Full Table Scans

ApproachDetail
Add appropriate indexB-tree for equality/range; GIN for arrays/JSON
Partition pruningFilter on partition key
Selective WHERELower fraction returned → index wins
Cluster (PG) / Clustered indexPhysically order rows

8. Using Query Caching

LayerDetail
Prepared statement cacheDriver-level; reuses parsed plan
Result cache (app)Redis/Memcached keyed by query
Materialized viewsPre-computed result on disk
MySQL query cacheRemoved in 8.0
Postgres pg_prewarmWarm buffer pool after restart

9. Optimizing Aggregate Queries

TechniqueDetail
Partial aggregationPre-aggregate per partition, then combine
Parallel aggregateMulti-worker hashagg (PG, MySQL HeatWave)
ApproximateHyperLogLog (approx_count_distinct) for COUNT DISTINCT
Materialize rollupsDaily summary tables
Avoid DISTINCT in aggTwo-stage GROUP BY often faster

10. Profiling Query Execution Time

ToolUse
pg_stat_statementsAggregate query stats over time
auto_explainLog slow plans automatically
MySQL slow loglong_query_time threshold
SQL Server Query StorePlan + runtime history
APM / OpenTelemetryEnd-to-end trace with DB span