Your Index Only Scan Is Lying: Covering Indexes and the Visibility Map
A covering index contains every column a query reads, so the database answers entirely from the index without visiting the table. In Postgres, the INCLUDE clause adds payload columns to leaf pages — they can't be searched or sorted on, but they eliminate the heap fetch that makes index scans expensive.
The catch: Postgres index-only scans depend on the visibility map. Each heap page has an all-visible bit set by VACUUM. If a page was recently modified and VACUUM hasn't caught up, the engine falls back to fetching the heap anyway. Heap Fetches in EXPLAIN ANALYZE is your diagnostic — any positive number means degraded pages. InnoDB doesn't have this problem because row versions live in the clustered index directly.
Covering indexes shine on hot read paths with stable data. On write-heavy tables where the visibility map can't stay current, you're paying index maintenance costs for a benefit that never materialises. Check Heap Fetches before celebrating that Index Only Scan.
Related Blogs
Composite Indexes Explained: Why Column Order Is the Whole Game
- Published on
- Reading time
- 4 min read
Views in PostgreSQL: A Complete Guide with Examples
- Published on
- Reading time
- 6 min read
How Database Indexes Work (and Why Yours Might Be Useless)
- Published on
- Reading time
- 6 min read