Query Optimization
Most slow-query fixes come from a short, repeatable checklist, not a deep bag of tricks.
Intermediate
SELECT * fetches every column, including large ones (a text blob, a jsonb column) the caller may not need — more I/O, more network transfer, and it defeats index-only scans, which need the specific columns used to be present in the index. It's also a schema-change trap: adding a column silently changes every caller's result shape.# N+1: one query per order, inside a loop
orders = db.query("SELECT * FROM orders WHERE customer_id = %s", customer_id)
for order in orders:
items = db.query("SELECT * FROM order_items WHERE order_id = %s", order.id) # ← N extra round trips
# fixed: one query, joined
rows = db.query('''
SELECT o.*, i.* FROM orders o
JOIN order_items i ON i.order_id = o.id
WHERE o.customer_id = %s
''', customer_id)
joinedload/selectinload, Django's select_related/prefetch_related) — the fix is almost never "write raw SQL instead," it's "tell the ORM to batch this relationship."-- NOT sargable — the index on created_at can't be used, the function runs on every row first
SELECT * FROM orders WHERE DATE(created_at) = '2026-08-01';
-- sargable — a plain range comparison the index can use directly
SELECT * FROM orders WHERE created_at >= '2026-08-01' AND created_at < '2026-08-02';
-- NOT sargable
SELECT * FROM orders WHERE amount * 1.1 > 100;
-- sargable — move the math to the constant side
SELECT * FROM orders WHERE amount > 100 / 1.1;
LIMIT restricts row count, but without an ORDER BY, which rows you get back is unspecified and can change between runs of the identical query — a subtle correctness bug that often masquerades as a performance question ("why did pagination skip a row").CREATE MATERIALIZED VIEW monthly_sales AS
SELECT date_trunc('month', order_date) AS month, SUM(amount) AS total
FROM orders
GROUP BY 1;
REFRESH MATERIALIZED VIEW monthly_sales; -- run on a schedule, or after a batch load
VIEW is just a saved query, re-run every time it's selected from — no performance benefit on its own. A MATERIALIZED VIEW actually stores the result and must be explicitly refreshed; the right tool when a summary is expensive to compute and doesn't need to be perfectly real-time.