SPB Git

spb/ultra-sharp-agent-skills Public

Ultra-Sharp Agent Skills — a research-first skill-authoring system + 72 production-ready skills for AI agents.

Python 100%

# Patterns — Optimizing SQL Performance

# Contents

  • Reading EXPLAIN ANALYZE
  • Finding the workload's worst queries
  • Index recipes
  • N+1 rewrites
  • Keyset pagination
  • Materialized views
  • Dialect deviations (MySQL, SQLite)
  • Gotchas

# Reading EXPLAIN ANALYZE

sql
EXPLAIN (ANALYZE, BUFFERS)
SELECT id, total_cents FROM orders
WHERE user_id = 42 AND created_at >= '2026-01-01';

Read inner-most node first; the fix usually targets the node with the largest actual time. Checklist per node:

Signal Meaning Action
Seq Scan + Rows Removed by Filter huge selective filter, no usable index add index (see recipes)
rows=3 estimated vs rows=30000 actual stale statistics ANALYZE orders; then re-plan
Sort Method: external merge Disk sort spills to disk index matching ORDER BY, or raise work_mem for the session
Nested Loop with huge outer side join misestimate fix stats; check join column indexes
Heap Fetches high on Index Only Scan visibility map stale VACUUM orders;

# Finding the workload's worst queries

sql
-- requires: CREATE EXTENSION pg_stat_statements;
SELECT calls, mean_exec_time::int AS mean_ms,
       (calls * mean_exec_time)::int AS total_ms, query
FROM pg_stat_statements
ORDER BY calls * mean_exec_time DESC
LIMIT 10;

Optimize by total_ms (aggregate cost), not by single-query time.

# Index recipes

sql
-- Equality-then-range composite (rule 3):
CREATE INDEX orders_user_created_idx ON orders (user_id, created_at);

-- Covering index for an index-only scan:
CREATE INDEX orders_user_created_cov_idx
    ON orders (user_id, created_at) INCLUDE (total_cents);

-- Expression index (query must use the same expression):
CREATE INDEX users_email_lower_idx ON users (lower(email));

-- Partial index for a hot subset:
CREATE INDEX orders_pending_idx ON orders (created_at)
    WHERE status = 'pending';

-- Trigram index for LIKE '%term%':
CREATE EXTENSION IF NOT EXISTS pg_trgm;
CREATE INDEX users_name_trgm_idx ON users USING gin (display_name gin_trgm_ops);

-- Build without blocking writes (production):
CREATE INDEX CONCURRENTLY ...;

-- Find unused indexes:
SELECT indexrelid::regclass, idx_scan
FROM pg_stat_user_indexes
WHERE idx_scan = 0 AND indexrelid::regclass::text NOT LIKE '%_pkey';

# N+1 rewrites

python
# ❌ one query per user
for uid in user_ids:
    cur.execute("SELECT * FROM orders WHERE user_id = %s", (uid,))

# ✅ one query, group in app code
cur.execute(
    "SELECT user_id, id, total_cents FROM orders WHERE user_id = ANY(%s)",
    (user_ids,))

Aggregate-per-parent variant in one round trip:

sql
SELECT u.id, COALESCE(SUM(o.total_cents), 0) AS spend
FROM users u
LEFT JOIN orders o ON o.user_id = u.id
WHERE u.id = ANY($1)
GROUP BY u.id;

# Keyset pagination

sql
-- page 1
SELECT id, created_at FROM orders
ORDER BY created_at DESC, id DESC LIMIT 20;

-- next page: pass the last row's (created_at, id)
SELECT id, created_at FROM orders
WHERE (created_at, id) < ($1, $2)
ORDER BY created_at DESC, id DESC LIMIT 20;

CREATE INDEX orders_created_id_idx ON orders (created_at DESC, id DESC);

Tie-break with id is mandatory — created_at alone skips/duplicates rows on equal timestamps. Tradeoff: no random page jumps.

# Materialized views

sql
CREATE MATERIALIZED VIEW daily_revenue AS
SELECT created_at::date AS day, SUM(total_cents) AS revenue_cents
FROM orders GROUP BY created_at::date;

CREATE UNIQUE INDEX daily_revenue_day_idx ON daily_revenue (day);
REFRESH MATERIALIZED VIEW CONCURRENTLY daily_revenue;  -- needs the unique index

# Dialect deviations (MySQL, SQLite)

  • MySQL: EXPLAIN ANALYZE (8.0.18+); no partial/expression indexes before 8.0.13 (functional indexes); use performance_schema instead of pg_stat_statements; no INCLUDE — add columns to the index key.
  • SQLite: EXPLAIN QUERY PLAN; run ANALYZE; to populate stats; partial indexes supported; no concurrent index builds.

# Gotchas

  • CREATE INDEX (without CONCURRENTLY) takes a write lock on the table.
  • EXPLAIN without ANALYZE shows estimates only — plans, not reality.
  • Casts defeat indexes: WHERE id::text = $1 seq-scans; cast the parameter instead.
  • Low-selectivity indexes (boolean flags) are usually ignored by the planner — a partial index on the rare value works.
  • random_page_cost default (4.0) is tuned for spinning disks; on SSDs the planner may wrongly prefer seq scans — typical SSD setting is 1.1.
  • After bulk loads, run ANALYZE (and VACUUM) before judging any plan.
  • ORMs hide N+1: enable SQL logging before believing "the query is slow" — often it's 500 queries.