{"post":{"slug":"indexes-explained-2026","title":"Database indexes explained in 2026: what every developer must know","excerpt":"Indexes make or break query performance. Here is the 2026 guide covering B-tree, GIN, GiST, and partial indexes with concrete examples and tradeoffs.","category":"code","date":"April 6, 2026","author":"ByteLedger Team","content":"<span class=\"dropcap\">I</span>ndexes are the single highest-leverage performance tool in a relational database. A well-placed index turns a 30-second query into 3 milliseconds. A poorly thought-out index collection silently tanks write throughput and eats disk. This is the 2026 practical guide — no theory, just what to do and why.\n\n## What changed in 2026\n\n- **BRIN and bloom indexes** saw wider adoption for append-only time-series tables in Postgres 17+.\n- **pgvector HNSW indexes** are now the standard path for vector similarity search within Postgres, replacing IVFFlat for most use cases.\n- **`pg_stat_statements` and auto_explain** are enabled by default on major managed providers (Neon, Supabase, RDS), making index diagnosis easier.\n- **Invisible indexes** (MySQL 8.0+, MariaDB) let you test index removal without dropping, reducing risk in production.\n\n## Index types at a glance\n\n| Index type | Best for | Not good for |\n| --- | --- | --- |\n| B-tree (default) | Equality, range, ORDER BY | Full-text, JSONB containment |\n| Composite B-tree | Multi-column filtering | Queries skipping the leftmost column |\n| Partial B-tree | Filtered subsets (active rows) | Full-table scans |\n| Covering (INCLUDE) | Index-only scans | Wide rows with frequent updates |\n| GIN | Full-text search, JSONB, arrays | Numerical range queries |\n| GiST | Geometry, range types | Simple equality |\n| HNSW (pgvector) | ANN vector search | Exact nearest-neighbor at huge scale |\n\n## B-tree: the workhorse\n\n```sql\n-- Single-column index for equality / range\nCREATE INDEX idx_orders_user_id ON orders (user_id);\n\n-- Composite: leftmost first by selectivity\nCREATE INDEX idx_orders_status_created\n  ON orders (status, created_at DESC);\n-- Supports: WHERE status = 'pending' ORDER BY created_at DESC\n-- Does NOT use: WHERE created_at > '2026-01-01' (misses left prefix)\n```\n\nThe left-prefix rule is the most violated index principle. If you have `(a, b, c)`, queries filtering only on `b` or `c` cannot use this index.\n\n## Partial indexes: surgical precision\n\n```sql\n-- Only index rows that are 'pending' — the set you actually query\nCREATE INDEX idx_orders_pending\n  ON orders (created_at)\n  WHERE status = 'pending';\n```\n\nA partial index is often 10–100× smaller than a full index. Smaller index = fits in cache = faster scans. Use whenever you have a high-cardinality column you always filter alongside.\n\n## GIN for JSONB and full-text\n\n```sql\n-- JSONB containment queries\nCREATE INDEX idx_events_meta ON events USING GIN (metadata);\n-- Now this is fast:\nSELECT * FROM events WHERE metadata @> '{\"type\": \"click\"}';\n\n-- Full-text search\nCREATE INDEX idx_posts_search ON posts\n  USING GIN (to_tsvector('english', title || ' ' || body));\n```\n\nWithout a GIN index, every JSONB `@>` operator triggers a sequential scan of every row.\n\n## Covering indexes (INCLUDE)\n\n```sql\nCREATE INDEX idx_orders_user_covering\n  ON orders (user_id)\n  INCLUDE (total, created_at);\n-- Allows index-only scan for:\nSELECT user_id, total, created_at FROM orders WHERE user_id = 42;\n```\n\nThe INCLUDE columns are stored in the leaf pages but not in the B-tree key. Queries that touch only those columns never have to visit the heap.\n\n## How to pick the right index\n\n1. **Run `EXPLAIN (ANALYZE, BUFFERS)`** on the slow query. Look for `Seq Scan` on large tables — that is where you need an index.\n2. **Check cardinality.** Low-cardinality columns (boolean, small enum) may not benefit from an index; the planner may prefer a seq scan under 5–10% selectivity.\n3. **Start with the WHERE clause columns**, then cover ORDER BY, then add INCLUDE for SELECT columns.\n4. **Use partial indexes** when you always filter on a specific value alongside the indexed column.\n5. **Use `pg_stat_user_indexes`** to find indexes with zero scans — those are candidates for removal.\n\n## Common mistakes\n\n**Over-indexing.** Every index slows down INSERT/UPDATE/DELETE because all indexes must be maintained. A table with 20 indexes on every column will write at a fraction of its potential speed.\n\n**Wrong column order in composite indexes.** Always put equality-filter columns first, range-filter columns last, and ORDER BY columns at the end.\n\n**Indexing a nullable column without accounting for NULLs.** Postgres B-tree stores NULLs but they cannot satisfy `IS NOT NULL` bounds scans efficiently. Use a partial index `WHERE col IS NOT NULL` if you never query NULLs.\n\n**Not monitoring index bloat.** Dead tuples accumulate in MVCC databases. Run `VACUUM` or check `pgstattuple` regularly; bloated indexes are slow even if they exist.\n\n**Ignoring write amplification.** On write-heavy tables (>10k inserts/s) every extra index costs real throughput. Profile writes with and without the index before adding.\n\n## What to skip\n\n- **Indexing every foreign key blindly.** Foreign keys on tiny lookup tables (10 rows) need no index; the planner will prefer a seq scan.\n- **GiST for non-spatial data.** Unless you have geometry or range types, B-tree is faster and simpler.\n- **Index hints in ORMs.** If you need to force an index hint, the query or schema is wrong. Fix the root cause.\n\n## FAQ\n\n**How do I know if an index is being used?**\nRun `EXPLAIN (ANALYZE)` and look for `Index Scan` or `Index Only Scan` nodes. Also query `pg_stat_user_indexes` for `idx_scan` counts in production.\n\n**Can I add an index without locking the table?**\nYes: `CREATE INDEX CONCURRENTLY` in Postgres builds the index without an exclusive lock, though it takes longer. Always use this in production.\n\n**How many indexes is too many?**\nThere is no universal number. A read-heavy reporting table can support 15 indexes; a high-throughput event log should have 2–3. Measure write latency under load.\n\n**Should I index UUID primary keys?**\nYes, but use UUIDv7 (time-ordered) instead of UUIDv4. Random UUIDs cause index fragmentation and page splits because inserts land in random B-tree positions.\n\n## Where to go next\n\n- [SQL joins explained in 2026](/blog/sql-joins-explained-2026)\n- [Transactions explained in 2026](/blog/transactions-explained-2026)\n- [How to set up Postgres locally in 2026](/blog/how-to-set-up-postgres-locally-2026)","hue":309,"metaTitle":"Database Indexes Explained 2026: B-tree, GIN & More","metaDescription":"2026 guide to database indexes: B-tree, composite, partial, GIN, GiST, and covering indexes. Learn when to add, what to avoid, and how to diagnose.","keywords":["database indexes explained 2026","postgres index types 2026","b-tree index sql","composite index column order","gin index jsonb","partial index postgres","covering index sql","index bloat database","how to use explain analyze 2026","sql index performance 2026"],"tldr":["**B-tree is the default** — covers equality, range, and ORDER BY; use it for 90% of cases.","**Composite indexes follow left-prefix rules** — column order matters more than most developers realise.","**GIN indexes power full-text and JSONB queries** — without one, a JSONB containment check is a full scan.","**Partial indexes eliminate index bloat** — index only the rows you actually query.","**Skip:** indexing every column \"just in case\" — write overhead and index bloat will hurt insert throughput."],"readTime":"4 min read","wordCount":957},"related":[{"slug":"database-indexing-explained-2026","title":"Database indexing explained in 2026: what every developer must know","excerpt":"Bad indexes cause more production incidents than almost any other root cause. Here is the 2026 guide to choosing, building, and maintaining the right ones.","category":"code","date":"March 10, 2026","author":"ByteLedger Team","readTime":"5 min read","hue":318},{"slug":"how-to-optimize-sql-queries-2026","title":"How to optimize SQL queries in 2026: indexes, plans, and real fixes","excerpt":"Slow SQL is rarely a mystery — the query plan tells you exactly what is wrong. This 2026 guide covers the tools and techniques to find and fix slow queries fast.","category":"code","date":"April 1, 2026","author":"ByteLedger Team","readTime":"5 min read","hue":309},{"slug":"what-is-a-composite-index-2026","title":"What is a composite index? The 2026 guide to column order","excerpt":"A composite index covers more than one column, and the order of those columns changes which queries it can actually help. Here is how to get it right.","category":"code","date":"July 17, 2026","author":"ByteLedger Team","readTime":"4 min read","hue":200}],"sameCategory":[{"slug":"bazel-build-explained-2026","title":"Bazel Explained: What Hermetic Builds Actually Cost","excerpt":"Bazel delivers reproducible, precisely cached builds and demands that you describe everything. Here is what you get, what it costs, and who should say no.","category":"code","date":"August 19, 2026","author":"ByteLedger Team","readTime":"3 min read","hue":145},{"slug":"graphql-federation-2026","title":"GraphQL Federation: One Graph, Many Teams","excerpt":"Federation composes independently-owned subgraphs into a single API. Here is what it solves, the operational cost it adds, and when a simpler gateway is enough.","category":"code","date":"August 19, 2026","author":"ByteLedger Team","readTime":"3 min read","hue":300},{"slug":"hermetic-builds-explained-2026","title":"Hermetic Builds Explained: Same Inputs, Same Output","excerpt":"A hermetic build depends only on declared inputs. Here is what that property gives you, the common leaks that break it, and how to move toward it incrementally.","category":"code","date":"August 19, 2026","author":"ByteLedger Team","readTime":"3 min read","hue":130},{"slug":"monorepo-tooling-compared-2026","title":"Monorepo Tooling Compared: What You Actually Need","excerpt":"Monorepo tools solve task orchestration and caching, not repository layout. Here is what each tier provides and how to tell when you have outgrown the simple option.","category":"code","date":"August 19, 2026","author":"ByteLedger Team","readTime":"3 min read","hue":150}],"newer":{"slug":"index-funds-vs-etfs-2026","title":"Index funds vs ETFs in 2026: which wrapper is right for your money","excerpt":"Index funds and ETFs both track indexes cheaply, but they differ in how you buy them, tax treatment, and minimums. Here is which to use when in 2026.","category":"finance","date":"April 6, 2026","author":"ByteLedger Team","readTime":"5 min read","hue":278},"older":{"slug":"integration-testing-explained-2026","title":"Integration testing explained in 2026: test the seams that unit tests miss","excerpt":"Integration tests catch the failures that happen when two correct pieces connect incorrectly. Here is the 2026 guide to writing fast, reliable integration tests.","category":"code","date":"April 6, 2026","author":"ByteLedger Team","readTime":"4 min read","hue":305}}