Cheatsheets
PostgreSQL
EXPLAIN, index types, transactions & isolation, JSONB, VACUUM, and query tuning.
Visualize
Postgres — index vs sequential scan
A sequential scan checks every row (O(n)); a B-tree index takes a few guided hops (O(log n)).
checked 0 of 8 rows
took 0 of 3 hops
Press play — both race to find id=42.
68 entries
EXPLAIN & reading a plan7
EXPLAIN SELECT * FROM orders WHERE customer_id = 42;Show the planner's chosen plan WITHOUT running the query
EXPLAIN (ANALYZE, BUFFERS, VERBOSE)
SELECT * FROM orders WHERE customer_id = 42;Actually run the query and report real time, rows, and I/O
Seq Scan on orders (cost=0.00..18584.00 rows=1000000 ...)Sequential Scan — read every row in the table, no index used
Index Scan using idx_x on t ... Index Cond: (id = 5)Index Scan — walk the B-tree, then fetch matching heap rows
Bitmap Heap Scan ... -> Bitmap Index Scan on idx_xBitmap scan — collect row locations, sort, then read heap in order
Nested Loop / Hash Join / Merge JoinThe three join strategies — and when each wins
SET track_io_timing = on; -- then EXPLAIN (ANALYZE, BUFFERS)Add real I/O timing to BUFFERS so you see disk vs cache cost
Index types10
CREATE INDEX idx ON t (col); -- B-tree (default)B-tree — the default; equality, range, sorting, prefix LIKE
CREATE INDEX idx_doc ON t USING gin (data); -- jsonb / arrays / FTSGIN — inverted index for containment in jsonb, arrays, and full-text
CREATE INDEX idx_geo ON t USING gist (geom); -- ranges, geometry, KNNGiST — extensible tree for geometry, ranges, and nearest-neighbour
CREATE INDEX idx_ts ON events USING brin (created_at);BRIN — tiny index for huge, naturally-ordered append-only tables
CREATE INDEX idx_active ON users (email) WHERE deleted_at IS NULL;Partial index — index only the rows matching a predicate
CREATE INDEX idx_lower_email ON users (lower(email));Expression index — index the result of a function or expression
CREATE INDEX idx_ab ON t (a, b, c); -- leftmost-prefix ruleComposite index — column order matters (leftmost prefix)
CREATE INDEX idx_cov ON orders (customer_id) INCLUDE (status, total);Covering index (INCLUDE) — enable an index-only scan
CREATE INDEX CONCURRENTLY idx ON big_table (col);Build an index WITHOUT locking writes — for production tables
CREATE INDEX idx_hash ON t USING hash (col);Hash index — equality only, niche since B-tree usually wins
Query tuning recipes6
-- Make predicates sargable (Search ARGument ABLE)Don't wrap the indexed column in a function — it disables the index
ANALYZE orders; -- refresh planner statisticsRefresh statistics so the planner estimates rows correctly
-- SELECT only the columns you need (avoid SELECT *)Narrow column lists enable index-only scans and cut I/O
... ORDER BY id LIMIT 20 -- keyset, not OFFSETKeyset pagination beats OFFSET on large tables
-- Add an FK column index (Postgres does NOT create one)Foreign keys are not auto-indexed — index the referencing column
pg_stat_statements: ORDER BY total_exec_time DESCFind the queries that actually cost the most across the whole DB
Transactions & isolation7
SHOW transaction_isolation; -- default: read committedREAD COMMITTED (default) — each statement sees the latest committed data
BEGIN ISOLATION LEVEL REPEATABLE READ;REPEATABLE READ — one stable snapshot for the whole transaction
BEGIN ISOLATION LEVEL SERIALIZABLE;SERIALIZABLE — as if transactions ran one at a time
SELECT ... FROM jobs
WHERE status = 'pending'
ORDER BY created_at
FOR UPDATE SKIP LOCKED
LIMIT 1;Work-queue pattern — lock a row and let other workers skip it
SELECT ... FOR UPDATE; -- vs FOR SHARE / FOR NO KEY UPDATERow-level locking modes — block writers while you decide
-- Deadlock: txn A locks row 1 then 2; txn B locks 2 then 1Deadlocks — Postgres detects and aborts one victim automatically
SAVEPOINT sp1; ... ROLLBACK TO sp1;Savepoints — partial rollback within a transaction
MVCC & maintenance6
-- MVCC: every UPDATE/DELETE creates a dead tupleHow MVCC works — old row versions linger until VACUUM removes them
VACUUM (ANALYZE, VERBOSE) orders;VACUUM — reclaim dead tuples and update the visibility map
-- autovacuum: the background daemon (leave it ON)Autovacuum — automatic VACUUM/ANALYZE triggered by churn
VACUUM FULL orders; -- ACQUIRES AN EXCLUSIVE LOCKVACUUM FULL — rewrites the table to shrink it, but locks everything
-- Bloat: dead tuples + empty space inflate table/index sizeTable & index bloat — wasted space from churn and stalled VACUUM
SELECT datname, age(datfrozenxid) FROM pg_database;Transaction-ID wraparound — the silent cluster-killer VACUUM prevents
JSONB7
data -> 'user' -- returns jsonb
data ->> 'name' -- returns text-> keeps JSON, ->> extracts text — the core access operators
data #> '{a,b,0}' -- jsonb at path
data #>> '{a,b,0}' -- text at path#> / #>> — extract by a path array (deep navigation)
data @> '{"paid": true}'Containment — does the JSON contain this sub-document? (GIN-indexable)
data ? 'key' data ?| array['a','b'] data ?& array['a','b']Key-existence operators — does this top-level key exist?
SELECT jsonb_build_object('id', id, 'name', name) FROM users;Build JSON from rows — object / array / agg constructors
SELECT jsonb_path_query(data, '$.items[*] ? (@.qty > 1)') FROM orders;JSONPath — query and filter inside a JSON document (SQL/JSON path)
-- JSONB column vs real columns: choose deliberatelyWhen to use JSONB vs normalized columns
Constraints6
id bigint GENERATED ALWAYS AS IDENTITY PRIMARY KEYPrimary key — the modern IDENTITY way to auto-generate it
customer_id bigint REFERENCES customers (id) ON DELETE CASCADEForeign key — referential integrity, with an ON DELETE policy
email text NOT NULL UNIQUEUNIQUE — enforce no duplicates (backed by a unique index)
CHECK (price >= 0)CHECK — enforce a boolean invariant on each row
EXCLUDE USING gist (room_id WITH =, during WITH &&)EXCLUSION constraint — generalize UNIQUE to 'no two rows overlap'
ALTER TABLE t ADD CONSTRAINT fk ... DEFERRABLE INITIALLY DEFERRED;Deferrable constraints — postpone the check until COMMIT
Upsert & RETURNING4
INSERT INTO t (id, n) VALUES (1, 'a')
ON CONFLICT (id) DO UPDATE SET n = EXCLUDED.n;Upsert — insert, or update the existing row on a unique conflict
INSERT INTO t (...) VALUES (...) ON CONFLICT DO NOTHING;Insert-if-absent — skip silently when the row already exists
UPDATE t SET status = 'done' WHERE id = 5 RETURNING *;RETURNING — get the affected rows back from a write, no second query
WITH moved AS (
DELETE FROM inbox WHERE id = 5 RETURNING *
)
INSERT INTO archive SELECT * FROM moved;Data-modifying CTE — chain writes atomically in one statement
Postgres features8
WITH RECURSIVE tree AS (
SELECT id, parent_id FROM nodes WHERE id = 1
UNION ALL
SELECT n.id, n.parent_id FROM nodes n JOIN tree t ON n.parent_id = t.id
)
SELECT * FROM tree;Recursive CTE — traverse trees and graphs (hierarchies)
SELECT *, sum(amt) OVER (ORDER BY day
ROWS BETWEEN UNBOUNDED PRECEDING AND CURRENT ROW) AS running
FROM ledger;Window frames — running totals and moving windows (frame clause)
SELECT *
FROM customers c,
LATERAL (SELECT * FROM orders o
WHERE o.customer_id = c.id
ORDER BY o.created_at DESC LIMIT 3) recent;LATERAL join — a subquery that references the row to its left
area numeric GENERATED ALWAYS AS (w * h) STOREDGenerated columns — a column computed from other columns
tags text[] -- '{a,b,c}' tags @> '{a}' array_length(tags,1)Array columns — first-class arrays with operators and GIN indexing
CREATE TYPE mood AS ENUM ('low','ok','high');ENUM types — a fixed, ordered set of named values
id uuid PRIMARY KEY DEFAULT gen_random_uuid()UUID keys — gen_random_uuid() built into Postgres 13+
to_tsvector('english', body) @@ to_tsquery('english', 'cat & dog')Full-text search — tsvector/tsquery with a GIN index
Operations & psql7
-- Postgres connections are heavy: ~1 process + memory eachConnection pooling — why you need PgBouncer in front
SELECT pid, state, wait_event_type, query
FROM pg_stat_activity WHERE state <> 'idle';pg_stat_activity — see every connection and what it's doing right now
SELECT pg_cancel_backend(pid); -- cancel
SELECT pg_terminate_backend(pid); -- kill the connectionCancel or kill a query/connection by pid
SELECT * FROM pg_locks bl JOIN pg_stat_activity a USING (pid);pg_locks — find what is blocking what
GRANT SELECT ON ALL TABLES IN SCHEMA public TO readonly;Roles & GRANT — least-privilege access control
\d table \dt \di \l \timing \xEssential psql meta-commands — inspect schema and tune the prompt
\copy orders TO 'orders.csv' WITH (FORMAT csv, HEADER);\copy — fast bulk import/export through the psql client