First, the planner may be right. If the query matches a large share of the rows, or the table is small, a sequential scan is cheaper. Reading the table in order costs less than many random index lookups.
Otherwise, the query often cannot use the index. Common causes:
- A function or cast on the column, like
lower(email)orcreated_at::date. This needs an expression index (an index built on that expression). - A type mismatch. It can force a cast on the column side.
- A leading wildcard, like
LIKE '%abc'. A B-tree index cannot help here. EvenLIKE 'abc%'needs the C collation or atext_pattern_opsindex. - A composite index used without its leading column. If the query has no condition on the first column of the index, the index is a poor fit.
Stale statistics can also make the planner misjudge selectivity (how many rows a condition matches). Run ANALYZE to fix them. Confirm the plan with EXPLAIN ANALYZE. Use SET enable_seqscan = off only to test what the index plan would cost. Never use it as a fix.
- If a query matches many rows or the table is small, a sequential scan is cheaper.
- Functions, casts or type mismatches on the column block the index.
- A leading wildcard
LIKEcannot use a B-tree. A prefixLIKEneeds the C collation ortext_pattern_ops. - A composite index is a poor fit without a condition on its leading column.
- Stale statistics mislead the planner. Verify with
EXPLAIN ANALYZE.
Read more