Guide · The plan, node by node
Loops
loops=N is the last figure on every node of an EXPLAIN ANALYZE plan. It says how many
times the node ran. The rows and the time printed beside it are averages per run, not totals.
The sample's Index Scan using order_items_order_idx on order_items prints rows=3 loops=206880: 3 rows each time, 206,880 times, 620,640 rows in all. Index Scan using customers_pkey on customers prints rows=1 loops=620640.
Why it matters
Read the rows without the loops and the plan lies to you. An index scan that returns 3 rows sounds free. 206,880 of them cost more than the Seq Scan they hang off. The node under a Nested Loop runs once per outer row, so its loop count is the outer side's row count. When the outer estimate is 338× off, the loop count is 338× off with it. Under a Gather, loops is the number of processes.
What to do
- Multiply before judging: rows × loops, time × loops.
- If the loop count is far above the planner's estimate for the outer side, fix that
estimate first.
ANALYZE commerce.orderstakes the sample from 206,880 loops to one Hash Join pass. - If the loop count is right, make each loop cheaper: an index that answers the lookup from one page, or an Index Only Scan.
In the Plan tab
Seqora multiplies for you. Every band is rows × loops, and the loop count sits under the band
as ↻ ×N. Marker 4 is the one this page is about.
The row flow of the sample, with the loop label under the order_items scan.
The row flow of the sample, with the loop label under the order_items scan.
- The band into
Seq Scan on ordersis the 12,000,000 rows read in its one run. A scan that runs once has no loop label. 207K ┅ 612is the scan's output, 206,880 rows, against the planner's 612. Every node to the right of it under the Nested Loop runs once per one of these rows.−11.8Mis what the filter removed, in that one pass.↻ ×207K ┅ ×612under theorder_itemsscan is its loop count, 206,880 runs where the planner budgeted for 612. The band above it is already multiplied out: 3 rows × 206,880 runs, 620,640.1.64 sis the scan's self time for its one run. A looped node's time is already the total across its runs:EXPLAINreports it per loop, and Seqora multiplies it out before drawing the node, the inspector and the time strip.- The result end is 24 rows, once.
The inspector splits the two back out: a Rows per loop ruler, 3 against the planner's 3,
on target, and a Loops ruler, 206,880 against 612, 338× more.
Caveats
- PostgreSQL 17 rounds the per-loop rows to a whole number. A node that found one row in a
tenth of its loops prints
rows=0.