An index-only scan answers a query from the index alone, never touching the
heap. Postgres will only do it when two conditions hold.
Condition one: the index covers the query
Every column referenced — in SELECT, WHERE and ORDER BY — must be present
in the index. INCLUDE adds payload columns without widening the search key:
covering index
CREATE INDEX idx_orders_customer ON orders (customer_id) INCLUDE (status, total_cents);
Condition two: the visibility map is current
The index holds no row visibility information, so Postgres consults the
visibility map to learn whether a page is all-visible. Pages dirtied since the
last vacuum are not, and those rows require a heap fetch anyway.
Reading the plan
EXPLAIN (ANALYZE, BUFFERS) reports Heap Fetches. Zero means the scan was
genuinely index-only. A large number after a bulk load means the table has not
been vacuumed — run VACUUM ANALYZE and measure again before touching the
index.