Database field guide · 39
Index-Only, Loose, and Distinct Scans
Covering reads, skipping groups, and early stopping
Distinguish index-only coverage from loose, skip, and distinct scans, then design ordered keys that reduce heap visits and entries examined.
Franck Pachot7 chaptersindex-only scan · loose scan · skip scan · DISTINCT
Build the mental model before choosing the mechanism.
This minibook was AI-generated from Franck Pachot's archived blog posts. Links to the original articles are included for source context and verification.
01
Index-only describes data availability
An index-only scan can return referenced columns from an index, but PostgreSQL must still establish tuple visibility. Coverage and avoiding heap fetches are related but separate conditions.
- Design coverage per query projection.
- Inspect Heap Fetches at runtime.
- Recent writes can clear all-visible state.
02
The visibility map is a shortcut
Vacuum marks heap pages all-visible after proving no tuple needs per-row visibility checks. The executor can then trust index entries for those pages without visiting the heap.
- Connect maintenance state to read behavior.
- Do not expect stable zero heap fetches on hot pages.
- Measure all-visible coverage for the target relation.
03
Loose scans skip within ordered groups
A loose index scan repeatedly seeks to the next distinct leading-key value instead of reading every entry. Its work approaches the number of groups times tree navigation rather than total leaf entries.
- Require order compatible with the grouping key.
- Measure index entries examined.
- Account for repeated root-to-leaf searches.
04
Skip scans probe missing prefixes
A skip scan can use a compound index when the leading key is not constrained by enumerating or jumping across its distinct values. It is attractive only when that prefix has a manageable cardinality.
- Estimate distinct leading values.
- Compare with a sequential or full index scan.
- Keep product and version support explicit.
05
DISTINCT pushdown changes network work
In a distributed database, pushing distinct or loose scanning to tablet servers prevents every duplicate index entry from crossing the network. The SQL result is unchanged while storage reads and RPC payloads fall.
- Inspect rows at storage and coordinator boundaries.
- Test skew across tablets.
- Preserve ordering and null semantics.
06
Cover, skip, and stop are independent
A query can cover output yet scan many duplicate entries, or skip groups yet fetch heap tuples. The best design aligns key order, visibility, projection, filtering, and LIMIT so each form of avoided work is measured separately.
- Count heap fetches, keys read, and rows returned.
- Use INCLUDE only for payload.
- Budget the write cost of wider indexes.
07
Field manual
Concrete mechanics, diagnostic evidence, and executable patterns to carry into a real system.
01
Verify index-only behavior
The node name is not enough: Heap Fetches shows how often visibility required a table visit. Repeat after controlled vacuum and after representative writes.
EXPLAIN (ANALYZE, BUFFERS)
SELECT tenant_id, created_at, status FROM event
WHERE tenant_id=$1 ORDER BY created_at DESC LIMIT 20;
02
Measure visibility coverage
The visibility map reports all-visible pages that can support heap-free index-only execution. Extension installation should follow normal governance.
CREATE EXTENSION IF NOT EXISTS pg_visibility;
SELECT * FROM pg_visibility_map_summary('public.event');
03
Express a loose scan recursively
Where native skip scan is unavailable, a recursive next-key probe demonstrates the physical idea. It needs careful null handling and is not automatically the best production query.
WITH RECURSIVE keys(k) AS (
(SELECT min(category) FROM item)
UNION ALL
SELECT (SELECT min(category) FROM item WHERE category > keys.k)
FROM keys WHERE k IS NOT NULL
)
SELECT k FROM keys WHERE k IS NOT NULL;
04
Compare entries examined per group
A useful distinct-scan test records groups returned, index entries read, heap fetches, storage rows, and RPCs. Runtime alone cannot show where duplicates were eliminated.
EXPLAIN (ANALYZE, BUFFERS, DIST, COSTS OFF)
SELECT DISTINCT category FROM item ORDER BY category;
-- Compare groups returned with index/storage rows read.
08
Source articles
Optional deep dives with the complete experiments and product-version context behind this guide.