Why is my PostgreSQL query still slow after adding an index?
An index existing and an index being used are different things. The planner chooses a strategy based on its cost estimates, and it will ignore an index whenever it believes a sequential scan will be faster. The first step is therefore not to add another index but to run the query through EXPLAIN ANALYZE, which shows both the plan chosen and the actual time spent at each step.
Several things commonly stop an index from being used.
- Low selectivity. If the query matches a large fraction of the table, reading it sequentially genuinely is cheaper than jumping through an index and then fetching most of the rows anyway. This is the planner being right, not wrong.
- Stale statistics. The planner relies on collected statistics about data distribution. After a bulk load or a large change these can be badly out of date, and refreshing them by analyzing the table often fixes the plan on its own.
- A function or cast applied to the column. Indexing a column does not help a query that filters on a transformation of it, such as a lowercased comparison or a date cast. Either match the expression exactly or create an index on the expression itself.
- Leading column rules. A composite index can only be used from the left. An index on two columns will not serve a query that filters on the second alone.
- Leading wildcards. A pattern match that begins with a wildcard cannot use a standard btree index, because the index is ordered by prefix.
It is also worth confirming the index is the actual bottleneck. If EXPLAIN ANALYZE shows the index scan completing quickly and the time going into a sort, a join, or fetching many rows from the heap, then adding indexes will not help and the query itself needs restructuring.