An Index Only Scan can still report heap fetches. The index may contain every value required by the query, yet PostgreSQL must also establish that each matching tuple is visible to the current MVCC snapshot. Index entries do not carry enough tuple-visibility state to make that decision independently.
The visibility map supplies a page-level shortcut. When the heap page referenced by an index tuple is marked all-visible, the executor can accept the tuple’s visibility without reading that heap page. When the bit is clear, the executor visits the heap tuple and performs the normal visibility check.
This makes index-only execution dependent on both index coverage and heap-page state.
Index coverage is only one condition
A query is a candidate for an index-only scan when its required values can be returned by an index type that supports the operation. A B-tree index can satisfy this condition when all referenced columns are present as key or included columns.
That property concerns data availability. MVCC visibility is a separate property.
PostgreSQL stores tuple transaction metadata in the heap rather than duplicating full visibility state in secondary index entries. An index tuple points toward a heap tuple, but the executor cannot infer arbitrary snapshot visibility from the indexed values alone.
Without another source of visibility information, every matching index tuple would still require a heap access. The visibility map provides that source at page granularity.
All-visible state removes per-tuple heap checks
Each heap relation has a visibility-map fork. Current PostgreSQL formats track two properties for each heap page: all-visible and all-frozen. Index-only scans depend on the all-visible property.
An all-visible bit is a conservative assertion. When set, every tuple on the associated heap page is known to be visible to all transactions for which the assertion applies. The executor can therefore return values from the index without opening the heap page merely to inspect tuple visibility.
A clear bit has weaker meaning. It does not prove that a tuple is invisible. It only means the page lacks the all-visible guarantee, so the executor must inspect the heap tuple for a definitive answer.
This asymmetry is important. The map is an optimization structure, not a replacement for MVCC metadata.
Writes clear the shortcut
Data modification can invalidate an all-visible assertion. PostgreSQL clears visibility-map state when a page is modified so a later index-only scan does not rely on stale page-level visibility information.
A frequently updated table can therefore use an Index Only Scan plan while still performing many heap fetches. The plan node identifies the access strategy; it does not guarantee zero heap reads for every candidate tuple.
This behavior is visible in EXPLAIN (ANALYZE, BUFFERS) output. The Heap Fetches count on an index-only scan records cases where the executor had to consult heap tuples because the relevant pages lacked usable all-visible state.
A low count indicates that most candidate tuples came from pages whose visibility could be accepted from the map. A high count indicates that index coverage exists but page-level visibility state is providing less benefit.
Vacuum can restore all-visible state
Vacuum examines heap pages and can mark a page all-visible after it establishes that the page contains no tuple requiring visibility-sensitive cleanup. That transition allows later index-only scans to skip heap visits for index tuples pointing into the page.
The effect is workload-dependent. A mostly static relation can retain all-visible bits across a large fraction of its pages. A write-heavy relation continually clears bits on modified pages, reducing the period during which those pages support heap-free index-only reads.
Autovacuum timing therefore influences observed heap-fetch counts, but it is not the only factor. Update distribution, page density, long-running transactions, dead tuple state, and the fraction of the table touched between vacuum cycles all affect the map’s useful coverage.
The relevant boundary is page-level rather than row-level. One modification can clear the all-visible state for a page that also contains many otherwise stable tuples.
Included columns do not carry visibility metadata
INCLUDE columns can make additional values available from a B-tree index without making those values part of the search key. This can turn a query that previously required heap data into a physical candidate for index-only execution.
It does not alter the MVCC boundary. A covering index can hold every projected value and still require heap fetches whenever referenced heap pages are not all-visible.
This distinction prevents a common performance assumption: adding payload columns to an index does not by itself make execution heap-free. It removes one reason for heap access, while visibility-map state controls another.
Larger covering indexes also consume more storage and can increase write and cache costs. Their value depends on the read pattern and on whether the table’s visibility state lets the executor avoid enough heap work to justify the extra index payload.
Heap-fetch counts can change without a plan change
Two executions of the same query can both show Index Only Scan while producing different heap-fetch counts. A vacuum between executions can set all-visible bits. Subsequent writes can clear them again. Neither transition requires the planner to select a different node type.
As a result, plan shape alone does not fully describe the physical access performed by an index-only scan. Runtime counters expose whether the visibility shortcut was effective for the pages encountered during that execution.
This also separates logical index suitability from maintenance state. The index definition may remain unchanged while the cost profile shifts as heap pages move between all-visible and non-all-visible states.
The visibility map compresses a broad visibility fact
The map avoids storing snapshot-specific visibility data in every index entry. Instead, it records a stronger page-level fact that is safe to reuse across many scans.
That design keeps the auxiliary structure small relative to the heap and makes its successful checks cheap. The tradeoff is coarse granularity: a page either carries the all-visible guarantee or it does not. When the guarantee is absent, PostgreSQL falls back to the heap’s tuple metadata.
Index-only scans therefore occupy a boundary between index structure and heap maintenance. Index coverage makes heap-free data retrieval possible, while all-visible page state makes heap-free MVCC verification possible. Both conditions must align before a matching index tuple can avoid a heap fetch.