Indexing in PostgreSQL is a way to quickly search for data in your database. If your table’s data were books, indexing would be like a library catalog that lets you quickly find the book you want by title or author. With JSONB, it gets a bit trickier, because the data is stored in a structured format, not as separate rows and columns.
When your JSONB data starts to bloat up to the size of “a Harry Potter book, just without the illustrations,” searching inside that structure can get slow. For example, if you want to find all orders where a certain key "status" has the value "delivered", PostgreSQL has to go through all the records to do the search. That sounds like a job you wouldn’t want to do by hand, right?
Well, GIN and BTREE indexes are our heroes here, coming to the rescue and saving us from long waits!
Index Types for JSONB
GIN (Generalized Inverted Index)
The GIN index is specially made for working with structured data like arrays and objects, which makes it perfect for JSONB. It lets you index not the whole object, but the individual keys and values inside it. That means with GIN you can quickly find records containing certain keys, values, or combos.
Imagine a JSONB column with data like:
{"name": "Alice", "age": 25, "city": "Berlin"}
A GIN index creates an internal structure where the keys "name", "age", and "city" are linked to their values. So when we search for "name": "Alice", PostgreSQL already knows where to look — it doesn’t have to run through the whole table.
BTREE
The BTREE index is more old-school. It creates an ordered structure that lets you quickly find data by specific values. With JSONB, you can use a BTREE index if you’re looking for an exact match or if you have a fixed key (like you want to compare the whole JSONB value).
If your column has JSONB objects like:
{"name": "Bob", "age": 30}
A BTREE index can be useful if you’re searching for records where the whole object matches exactly.
{"name": "Bob", "age": 30}
Creating an Index for JSONB
Let’s first see how to create a GIN index. All you need is the magic CREATE INDEX command. Here’s how it looks:
-- Create a GIN index for a JSONB column
CREATE INDEX idx_jsonb_data ON orders USING GIN (data);
Where:
idx_jsonb_data— the name of the index.orders— the name of the table.data— the column withJSONBdata.
After you create this index, queries that search for keys or values inside JSONB will run faster.
Let’s say we have a table orders with a data column that holds JSONB:
| id | data |
|---|---|
| 1 | {"status": "pending", "total": 100} |
| 2 | {"status": "delivered", "total": 200} |
Query without an index:
-- Find all orders with status "delivered"
SELECT * FROM orders WHERE data @> '{"status": "delivered"}';
If the table is big, this query can take a while. But with a GIN index, it’ll run way faster.
How to Create a BTREE Index
To create a BTREE index, you need to tweak your approach a bit. Most of the time, to use BTREE with JSONB, you want to index not the whole object, but a part of it. Here’s an example:
-- Create a BTREE index for a specific key
CREATE INDEX idx_jsonb_total ON orders ((data->>'total'));
Check out (data->>'total'). That pulls out the value of the total key from the JSONB object, and that’s what gets indexed. Now, if you’re searching for orders where total = 100, PostgreSQL will use this index.
Here’s an example with the same data:
| id | data |
|---|---|
| 1 | {"status": "pending", "total": 100} |
| 2 | {"status": "delivered", "total": 200} |
Query:
-- Find all orders where total = 100
SELECT * FROM orders WHERE data->>'total' = '100';
With a BTREE index on data->>'total', this query will run a lot faster.
Comparing GIN and BTREE
| Feature | GIN | BTREE |
|---|---|---|
| What gets indexed? | Keys and values inside JSONB | Specific path or value |
| Best use case | Searching parts of an object | Searching for a specific value |
| Index creation speed | Slower | Faster |
| Search performance | Faster for complex structures | Faster for fixed values |
| Operator support | @>, ?, `? |
,?&` |
If you have complex JSONB structures and you often use operators like @> or ?, go with GIN. If you’re searching for specific values or fixed keys, BTREE might be your best bet.
Traps and Common Mistakes When Indexing JSONB
Working with JSONB indexing can be powerful, but there are a few gotchas you should watch out for.
- No index where you need one. If you often use JSONB data in filters (
WHERE), but haven’t created an index, your queries will be slow. - Over-indexing. If you create indexes for every possible JSONB key, it can slow down inserts and updates.
- Wrong index type. If your queries are complex and use operators like
@>or?, but you made aBTREEindex, you won’t get a performance boost. - Not knowing about paths. If you’re always accessing nested values but haven’t created an index for a specific path (like
data->>'some_key'), your query will still be slow.
Bottom Line: When to Use Which Index
- Use
GINif you have arrays or complex objects where you often search by keys and values. - Use
BTREEif you’re looking for exact matches or often access specific keys.
GO TO FULL VERSION