🎉 75% of content is free forever — Unlock Premium from $10/mo →
CW
💼 Servicesℹ️ About✉️ ContactView Pricing Plansfrom $10

Query Optimization Deep Dive — Execution Plans, Index Strategy & Performance Tuning

Advanced SQLPerformance⭐ Premium

Advertisement

Query Optimization Deep Dive

Advanced SQL

Make Your Queries Fast

Query optimization is the art of reducing I/O, minimizing row processing, and leveraging indexes. The difference between a slow and fast query can be 1000x — and interviews test whether you can identify and fix performance bottlenecks.

  • EXPLAIN ANALYZE — Read execution plans to find bottlenecks
  • Index Strategy — Composite, partial, and expression indexes
  • Predicate Pushdown — Filter early, join late
  • Rewriting — IN vs EXISTS, UNION vs UNION ALL

The #1 optimization rule: do less work. Filter before joining, limit early, and only select the columns you need.


EXPLAIN ANALYZE — Reading Execution Plans

-- PostgreSQL execution plan analysis
EXPLAIN (ANALYZE, BUFFERS, FORMAT TEXT)
SELECT
  d.department_name,
  COUNT(e.employee_id) AS emp_count,
  AVG(e.salary) AS avg_salary
FROM departments d
INNER JOIN employees e ON d.department_id = e.department_id
WHERE e.status = 'active'
GROUP BY d.department_name
ORDER BY avg_salary DESC;

Key Plan Nodes to Watch

Node TypeWhat It MeansOptimization Target
Seq ScanFull table scanAdd index on filtered column
Index ScanUsing an indexGood — verify it's the right index
Hash JoinBuilding hash tableCheck if smaller table is build side
SortIn-memory or disk sortAdd index to avoid sort
Nested LoopRow-by-row joinGood for small result sets

ℹ️

Key Insight: Always use EXPLAIN ANALYZE (not just EXPLAIN) to see actual execution times. The BUFFERS option shows I/O usage. Look for sequential scans on large tables as optimization targets.


Index-Only Scans

-- Create covering index for index-only scan
CREATE INDEX idx_emp_dept_salary ON employees (
  department_id, salary
) INCLUDE (employee_id, status);

-- Verify index usage
EXPLAIN (ANALYZE)
SELECT employee_id, salary
FROM employees
WHERE department_id = 5 AND status = 'active';

Join Optimization

-- Bad: Large table driving small table
SELECT *
FROM transactions t
INNER JOIN users u ON t.user_id = u.user_id
WHERE t.amount > 100;

-- Good: Use CTE to filter first
WITH filtered_transactions AS (
  SELECT user_id, amount
  FROM transactions
  WHERE amount > 100
)
SELECT *
FROM filtered_transactions ft
INNER JOIN users u ON ft.user_id = u.user_id;

Subquery vs JOIN Performance

-- Slow: Correlated subquery
SELECT
  department_id,
  (SELECT AVG(salary) FROM employees e2
   WHERE e2.department_id = e1.department_id) AS avg_salary
FROM employees e1;

-- Fast: Window function (scans once)
SELECT DISTINCT
  department_id,
  AVG(salary) OVER (PARTITION BY department_id) AS avg_salary
FROM employees;

-- Fast: JOIN with aggregation
SELECT d.department_id, AVG(e.salary) AS avg_salary
FROM departments d
INNER JOIN employees e ON d.department_id = e.department_id
GROUP BY d.department_id;

Predicate Pushdown

-- Bad: Filter after aggregation
SELECT * FROM (
  SELECT department_id, SUM(salary) AS total
  FROM employees
  GROUP BY department_id
) sub
WHERE total > 1000000;

-- Good: Filter in HAVING (pushed down)
SELECT department_id, SUM(salary) AS total
FROM employees
GROUP BY department_id
HAVING SUM(salary) > 1000000;

⚠️

Optimization Tip: Use CTEs to pre-filter data before joining. This reduces the number of rows participating in expensive operations like joins and sorts. In PostgreSQL 12+, use MATERIALIZED to force CTE materialization.


Materialized CTEs

-- Force CTE materialization in PostgreSQL 12+
WITH active_users AS MATERIALIZED (
  SELECT user_id, username, email
  FROM users
  WHERE status = 'active'
    AND last_login > CURRENT_DATE - INTERVAL '30 DAY'
)
SELECT au.user_id, COUNT(o.order_id) AS order_count
FROM active_users au
LEFT JOIN orders o ON au.user_id = o.user_id
GROUP BY au.user_id;

Index Strategy for Queries

-- Composite index for multi-predicate queries
CREATE INDEX idx_orders_status_date_amount ON orders (
  status, order_date, amount
);

-- Partial index for selective queries
CREATE INDEX idx_active_users ON users (email)
WHERE status = 'active';

-- Expression index for function-based queries
CREATE INDEX idx_users_lower_email ON users (LOWER(email));

Query Rewriting

-- IN vs EXISTS (EXISTS often faster for large subqueries)
SELECT * FROM users
WHERE user_id IN (SELECT user_id FROM orders);

SELECT * FROM users u
WHERE EXISTS (
  SELECT 1 FROM orders o WHERE o.user_id = u.user_id
);

-- UNION vs UNION ALL (UNION adds DISTINCT overhead)
SELECT user_id FROM orders_2023
UNION ALL  -- No duplicate check
SELECT user_id FROM orders_2024;

Batch Processing

-- Process large updates in batches
WITH batch AS (
  SELECT order_id
  FROM orders
  WHERE status = 'pending'
  LIMIT 1000
  FOR UPDATE SKIP LOCKED
)
UPDATE orders
SET status = 'processing'
WHERE order_id IN (SELECT order_id FROM batch);

Statistics and Planning

-- Update table statistics
ANALYZE employees;

-- Check table bloat
SELECT
  pg_size_pretty(pg_total_relation_size('employees')) AS total_size,
  pg_size_pretty(pg_relation_size('employees')) AS table_size,
  pg_size_pretty(pg_indexes_size('employees')) AS index_size;

-- Check index usage
SELECT indexrelname, idx_scan, idx_tup_read, idx_tup_fetch
FROM pg_stat_user_indexes
WHERE relname = 'employees'
ORDER BY idx_scan DESC;

Quiz: Test Your Knowledge


Follow-Up Questions

  1. How do you identify slow queries in a production environment?
  2. What's the difference between a table scan and an index scan?
  3. How do you optimize queries that use multiple OR conditions?
  4. Explain the concept of query plan caching.
  5. How do you handle query optimization for partitioned tables?

Key Takeaways

  • EXPLAIN ANALYZE — Always use ANALYZE + BUFFERS to see actual times and I/O
  • Filter early — CTEs and subqueries to reduce rows before expensive joins
  • Index strategy — Composite indexes for multi-predicate, partial for selective
  • UNION ALL > UNION — Skip duplicate removal when possible
  • EXISTS > IN — Often faster for large subqueries (correlated)
  • ANALYZE — Update statistics so the optimizer picks the right plan
🔒

Premium Content

Query Optimization Deep Dive — Execution Plans, Index Strategy & Performance Tuning

You've previewed the first section. Unlock this full lesson and 900+ advanced tutorials with a Premium plan.

🎯End-to-end Projects
💼Interview Prep
📜Certificates
🤝Community Access

Already a member? Log in

Advertisement