Guide · The plan, node by node
Index Only Scan
An Index Only Scan answers the query from the index and never opens the table. It is
possible when every column the query needs is in the index, as a key or in its INCLUDE
list. invoices_customer_created_idx is (customer_id, created_at DESC) INCLUDE (status),
so SELECT created_at, status FROM billing.invoices WHERE customer_id = $1 can be answered
by it without touching the heap.
Why it matters
The catch is visibility. An index does not know which transactions can see a row, so
PostgreSQL checks the visibility map. That is one bit per page, set when every row on it is
visible to everyone. Pages with the bit are skipped. Pages without it force a heap fetch, the read
the scan was meant to avoid. EXPLAIN ANALYZE prints Heap Fetches: N. Zero is the goal. A
count near the rows returned means you are paying for an Index Scan.
invoices has had 1,000,000 updates, and each one clears the bit on its page until the next
VACUUM sets it again.
What to do
VACUUM billing.invoices. Only vacuum sets visibility map bits.ANALYZEdoes not.- If the fetches come back, the table changes faster than autovacuum keeps up. Lower its threshold:
ALTER TABLE billing.invoices SET (autovacuum_vacuum_scale_factor = 0.02);
- An Index Scan where you expected an Index Only Scan means a column is missing from the
index.
INCLUDEadds it without making it part of the key.
In the Plan tab
The wheel matches the Index Only Scan pattern, the fourth of the five the TABLE WHEEL
guide draws.
The five ways a scan reaches a table, as the TABLE WHEEL guide draws them.
The five ways a scan reaches a table, as the TABLE WHEEL guide draws them.
Seq Scanreads the whole rim: every page, whatever the query needed.Index Scan · in orderfetches every row from the table through the spokes, in a short arc. Each spoke is a page read the Index Only Scan avoids.Index Scan · scatteredfetches the same way, across the whole rim. The cost the Index Only Scan saves is largest here.Index Only Scanis this node: the hub answers the query and the rim stays dark. The lit segments are heap fetches, pages the visibility map could not vouch for, and each one is the read the scan was meant to avoid.Bitmap Heap Scancollects the pages first and reads each once. It still reads the heap.
In the guide for a real wheel the fetches are labelled Lit · heap fetches. The count itself
is not in the inspector. Hover the wheel's spokes for an Index lookups card with
Heap fetches and the number, or the rim for Heap visited as a share of the table. The raw
figure is in Raw plan JSON as Heap Fetches.
Caveats
- An Index Only Scan can carry a
Filteron a column the index lacks. The heap is fetched for it anyway, and the node is an Index Scan in all but name.