We’ve already dived into index theory, checked out their types, learned how to create and drop them, and figured out how to index tricky data types like arrays and JSONB. Now it’s time to talk about how to pick the index that’ll actually work best for your use case—because picking the wrong one can cause some serious headaches.
Imagine your database is a library, and queries are visitors looking for books. If the books are just scattered all over the floor, finding anything turns into an endless wandering. Indexes are like organized shelves and catalogs that help you find what you need fast, without having to look through everything.
But if you set up the wrong shelf or catalog—like using a HASH index where you really need a range search index—it’s like the librarian trying to find books by title, but only having a catalog sorted by publication year. The process drags on, and everyone starts complaining. In your database, this means slow queries and extra load on the system.
Today we’ll break down how to pick the right index so your queries fly and your database doesn’t get tired. If you mess up, the result is ugly: slow queries, wasted resources, and the librarian (PostgreSQL) sitting in a funk.
Index Selection Criteria: Checklist
When you’re picking an index, ask yourself a few questions:
- What’s the data type in this column?
- For example, numbers like
INTEGER,FLOATusually need aB-TREEindex, arrays—GIN, text fields—it depends on the task.
- For example, numbers like
What queries do you run most often?
WHERE field = value? Direct lookup? You’ll probably wantB-TREEorHASH.- Searching arrays or JSONB? Check out
GIN. - Geo data, ranges? Think about
GiST.
What’s happening with your data?
- If your table gets a lot of inserts and updates, avoid over-indexing, since that adds overhead.
Do you need to enforce uniqueness?
- In that case, you’ll need an index with the
UNIQUEattribute.
- In that case, you’ll need an index with the
Cases: Real-Life Index Selection Examples
Let’s check out a few real-world scenarios.
1. Simple Equality Search
You’re working with a student database and want to quickly find a student by their email:
SELECT * FROM students WHERE email = 'student@example.com';
What matters here? We’re searching by equality. The best choice is a B-TREE index, since it’s great for exact matches.
CREATE INDEX idx_students_email ON students (email);
Or, if email needs to be unique:
CREATE UNIQUE INDEX idx_students_email_unique ON students (email);
2. Range Search
Now let’s say you want to find students older than 18:
SELECT * FROM students WHERE age > 18;
For range searches, B-TREE is also a solid pick, since its structure is made for ordered lookups.
CREATE INDEX idx_students_age ON students (age);
3. Filtering by Arrays
You’ve got a courses table, where one column stores an array of student IDs enrolled in the course. You want to find all courses that student with ID 123 is enrolled in.
SELECT * FROM courses WHERE student_ids @> ARRAY[123];
For these queries, a GIN index is perfect, since it’s optimized for arrays.
CREATE INDEX idx_courses_students_ids ON courses USING gin (student_ids);
4. Extracting Data from JSONB
Let’s say you have a table with JSONB data storing order info. You want to find all orders where the client is from "Moscow":
SELECT * FROM orders WHERE data->>'city' = 'Moscow';
For this query a GIN index over the entire column is no help — the ->> operator isn't indexable via jsonb_ops/jsonb_path_ops. You need either an expression index on the specific key, or to rewrite the query with the @> operator:
-- Option 1: expression index
CREATE INDEX idx_orders_city ON orders ((data->>'city'));
-- Option 2: use @> with GIN
CREATE INDEX idx_orders_data ON orders USING gin (data);
SELECT * FROM orders WHERE data @> '{"city": "Moscow"}';
5. Geographic Data
If you’re working with geo info—like finding all points within a certain radius—use a GiST index. This index type is awesome for geometry and ranges.
CREATE INDEX idx_locations_geom ON locations USING gist (geom);
Performance Comparison of Different Indexes
Let’s take a real example: searching students by email. The table has 1 million rows. Exact numbers depend on the specific system and data distribution, but the order of magnitude is:
| Scenario | Execution Time |
|---|---|
| No index | ~1500 ms |
With B-TREE index |
~2-3 ms |
With HASH index |
~1-2 ms |
For strict equality (=) a HASH index is often slightly faster than B-TREE because it does an O(1) lookup versus O(log n) for B-TREE. However, B-TREE is more universal: it works not only for = but also for ranges, sorting, and LIKE with a prefix — so B-TREE remains the default choice.
Mistakes When Choosing Indexes
The most common mistake is creating indexes “just in case.” For example, you decide to index every column in your table, but then notice that insert performance tanks. Remember, an index isn’t a magic tool that always works everywhere. It’s a powerful tool in the right hands, but using it wrong can hurt.
Another classic mistake is picking the wrong index type. Say you use a HASH index for a range search, and your queries suddenly get super slow. That’s because HASH indexes are only good for exact lookups.
Index Selection Tips
- If you often do equality searches or sorting, use
B-TREE. - For exact matches with minimal memory, you can use
HASH. - If you’re working with arrays or JSONB, go with
GIN. - For ranges or geo data, use
GiST.
And finally, the main tip: always analyze your queries! Use EXPLAIN and EXPLAIN ANALYZE to see how PostgreSQL uses indexes and what you can improve.
EXPLAIN ANALYZE
SELECT * FROM students WHERE email = 'student@example.com';
That’s it for today! Now you’re ready to pick indexes like a Jedi picks their lightsaber. Be careful, don’t create indexes where you don’t need them, and always check how they affect performance.
GO TO FULL VERSION