GGistDev

Performance and EXPLAIN

Understand query plans, avoid common anti‑patterns, and tune with indexes and statistics.

EXPLAIN and ANALYZE

-- See the plan (estimated)
EXPLAIN SELECT * FROM orders WHERE customer_id = 1 ORDER BY created_at DESC LIMIT 10;

-- Run and measure (actual)
EXPLAIN ANALYZE SELECT * FROM orders WHERE customer_id = 1 ORDER BY created_at DESC LIMIT 10;

Read the plan top‑down: look for sequential scans vs index scans, join algorithms (nested loop / hash / merge), and row estimates vs actuals.

Statistics and ANALYZE

Keep table statistics fresh for good estimates.

-- Postgres
ANALYZE;          -- or autovacuum handles regularly

Skewed data may need higher sampling targets or extended stats (engine‑specific).

Index use

  • Match predicates and ORDER BY to available indexes
  • Prefer sargable predicates (search‑arg): avoid wrapping indexed columns in functions
-- bad: function prevents index use
WHERE LOWER(email) = 'a@example.com'
-- good: expression index (or store canonical form)
CREATE INDEX ON users ((lower(email)));

Anti‑patterns

  • SELECT * (wider I/O; specify columns)
  • Leading wildcard LIKE '%x' (prevents index use)
  • Large OFFSET pagination (prefer keyset/seek)
  • Functions on indexed columns without expression indexes
  • Implicit casts causing type mismatches

Joins and data access

  • Join on indexed keys; avoid cross join explosions
  • Use appropriate join order; sometimes rewriting as EXISTS is faster
  • Precompute aggregates with materialized views when queries are heavy and data changes slowly

Partitioning

Partition large tables by date/range/hash to prune partitions and keep indexes small.

Caching and connection settings

  • Use query result caches at the app layer when appropriate
  • Right‑size connection pools; too many concurrent queries can thrash caches

Summary

  • Use EXPLAIN (ANALYZE) to inspect reality; align predicates with indexes
  • Avoid anti‑patterns; consider partitioning and precomputation for scale