I noticed one of my SQL queries is much slower than I expected it to be, and it turns out that the query planner is coming up with a plan that seems really bad to me. My query looks like this:
select A.style, count(B.x is null) as missing, count(*) as total
from A left join B using (id, type)
where A.country_code in ('US', 'DE', 'ES')
group by A.country_code, A.style
order by A.country_code, total
B has a (type, id) index, and A has a (country_code, style) index. A is much smaller than B: 250K rows in A vs 100M in B.
So, I expected the query plan to look something like:
- Use the index on A to select just those rows with appropriate
country_code - Left join with B, to find the matching row (if any) based on its
(type, id)index - Group things according to
country_codeandstyle - Add up the counts
But the query planner decides the best way to do this is a sequential scan on B, and then a right join against A. I can't fathom why that is; does anyone have an idea? Here's the actual query plan it generated:
Sort (cost=14283513.27..14283513.70 rows=171 width=595)
Sort Key: a.country_code, (count(*))
-> HashAggregate (cost=14283505.22..14283506.93 rows=171 width=595)
-> Hash Right Join (cost=8973.71..14282810.03 rows=55615 width=595)
Hash Cond: ((b.type = a.type) AND (b.id = a.id))
-> Seq Scan on b (cost=0.00..9076222.44 rows=129937844 width=579)
-> Hash (cost=8139.49..8139.49 rows=55615 width=28)
-> Bitmap Heap Scan on a (cost=1798.67..8139.49 rows=55615 width=28)
Recheck Cond: ((country_code = ANY ('{US,DE,ES}'::bpchar[])))
-> Bitmap Index Scan on a_country_code_type_idx (cost=0.00..1784.76 rows=55615 width=0)
Index Cond: ((country_code = ANY ('{US,DE,ES}'::bpchar[])))
Edit: following a clue from the comments on another question, I tried it with SET ENABLE_SEQSCAN TO OFF;, and the query runs ten times as fast. Obviously I don't want to permanently disable sequential scans, but this helps confirm my otherwise-baseless guess that the sequential scan is not the best plan available.
EXPLAINor get an execution plan, put the results in the question as well. If you have no indexes, visit use-the-index-luke.com ASAP.b(type,id)alone is not enough. You are also using the columnb.xso after the index lookup Postgres would need another I/O operation to fetch the corresponding value for x. Anexplain analyzewould also be helpful to see the actual row counts and runtimes. You might try a covering index onb(id, type, x)explain (analyze, buffers)for both versions (with and without the enable_seqscan=off).b.xbe NULL? What do you want to count exactly? How many different values ina.country_code? There may be a faster query ... (and sorry, cardinalities are there, missed that at first)