JSONB, Trigram and Why an Index Is Ignored
Build GIN indexes for JSONB and trigram search on a real PostgreSQL stand, then systematically diagnose seven reasons the planner ignores an index — and learn to fix each one.
Most indexes work — until they don't. This course starts with a real stand: three tables, 720k rows, and a fixed random seed so every plan you read matches what is documented here. We cover two index types that standard B-tree cannot handle: GIN for jsonb containment queries and GIN with pg_trgm for contains-style LIKE search. Then we walk seven distinct reasons the planner quietly ignores an existing index — a cast on the column, a type mismatch, low selectivity, stale statistics, a composite index with the wrong key order, a partial index whose predicate the query doesn't satisfy, and a table so small that a sequential scan is just faster. Each reason gets its own EXPLAIN, its own diagnosis, and its own fix. The course ends with a living catalog file you keep and grow on real projects.
What you'll build
- Create a GIN index on a jsonb column and explain which operators it supports
- Name one case where a GIN index on jsonb does not help
- Install pg_trgm and create a GIN index for LIKE '%text%' search
- Tell prefix search (LIKE 'text%') apart from contains search (LIKE '%text%') in terms of index strategy
- List seven reasons the planner ignores an index and diagnose each with EXPLAIN ANALYZE
- Fix a function-on-column, type-mismatch, and low-selectivity case
- Use BUFFERS to verify an index is being read and not just the heap
- Maintain queries/index-catalog.sql as a living pattern-to-index reference
Contents
- A stand built for index experiments
- Wait for it to be ready
- One command to bring it up
- Start the server
- A short way into the database
- The events table
- The documents table
- The products table
- Create the tables
- Seed the events, repeatably
- Seed the documents
- Seed the products
- Load the data
- Fresh statistics, single-threaded plans
- Confirm what we are sitting on
- Searching inside JSONB without an index
- A sequential scan through half a million rows
- Build a GIN index on the JSONB column
- The same query, now with a GIN index
- A Bitmap Index Scan on the GIN index
- One operator that GIN cannot use
- Back to a sequential scan
- A contains LIKE with no index
- Sequential scan on documents
- Install pg_trgm and build the trigram index
- The same LIKE, now with a trigram index
- A Bitmap Index Scan through the trigram index
- A B-tree index for prefix search
- Prefix versus contains in one file
- Two patterns, two indexes
- Reason 1: a function wraps the indexed column
- The index is there but the cast breaks the match
- Reason 2: comparing the wrong type
- Sequential scan on the primary key table
- Reason 3: the index exists but selectivity is too low
- Too many matching rows for the index to help
- Reason 4: statistics are stale after a large insert
- The estimate is wildly wrong
- ANALYZE fixes the estimate
- Reason 5: composite index with the wrong key order
- The second column alone cannot use the index
- Reason 6: partial index whose predicate the query does not satisfy
- A partial index only works when its predicate is implied
- Reason 7: the table is too small to benefit from an index
- Sequential scan on a 100-row table
- A catalog you grow on real projects