Tid Range Scan
Appears in EXPLAIN asTid Range Scan
Scans a contiguous range of physical row locations.
What it is
A Tid Range Scan reads a contiguous span of ctids — for example WHERE ctid >= '(100,0)' AND ctid < '(200,0)' — which maps to a range of heap blocks.
When the planner picks it
When the query restricts ctid to a range. It's mostly used for chunked, parallelizable full-table processing.
Is it good or bad?
Useful for splitting a big table into block ranges for batch jobs. Like Tid Scan, it relies on physical addresses that aren't stable across row movement.
In depth
Scanning a range of physical addresses
A Tid Range Scan reads rows whose CTID falls within a contiguous range of
(block, offset) pairs:
SELECT *
FROM big
WHERE ctid >= '(0, 0)' AND ctid < '(10000, 0)';
That maps to "give me the rows from blocks 0 up to (but not including) 10000." Compared to a Tid Scan's single-point fetch, a Tid Range Scan is a mini sequential read of a block range — efficient and forward-only.
Why it's useful
The use case is splitting a huge table into block ranges for parallelizable
batch processing. Each worker takes a CTID range, scans only its slice,
and never overlaps with another worker's pages. With the table's size known
from pg_class.relpages, you can divide it evenly:
-- worker N of K:
SELECT *
FROM big
WHERE ctid >= ctid_lo
AND ctid < ctid_hi
AND … -- your business filter
This pairs well with external job queues and Map-Reduce-style jobs over large tables where you can't (or don't want to) use a partition column.
Same instability caveat
CTID ranges still aren't durable: VACUUM FULL, CLUSTER, and pg_repack
rewrite the table and shift every row. Use Tid Range Scan inside a single
transactional pass — produce the range, do the work, finish — and never
persist the ranges as keys.