Skip to content
In this guide

Guide · The plan, node by node

Seq Scan

A Seq Scan reads a table from its first page to its last and tests every row against the WHERE clause. In the sample plan, Seq Scan on commerce.orders reads all 12,000,000 orders to find the 206,880 placed on the web in the last 30 days, and throws the other 11,793,120 away. That one node takes 1,640 ms of the query's 4,581 ms.

Why it matters

A Seq Scan is right when the query needs most of the table. It is the problem when the table is large and the filter keeps little. The ratio to look at is rows kept over rows read. orders keeps 1.7%. Seqora flags a scan that keeps 20% or less of 10,000 rows or more. There is no index starting with channel, so the planner had nothing better.

What to do

  1. Read the filter. Equality columns go first in the index, range columns after: channel, then placed_at.
  2. Create it:
CREATE INDEX CONCURRENTLY orders_channel_placed_idx
  ON commerce.orders (channel, placed_at DESC);
  1. Run the query again and confirm the node is now an Index Scan or a Bitmap Heap Scan. If it is still a Seq Scan, read Will this index help? before dropping anything.

In the Plan tab

The wheel beside the node is the table. Double-click it for the TABLE WHEEL guide, which labels it the way the figure does.

The wheel beside Seq Scan on orders, with the guide's own labels.

The wheel beside Seq Scan on orders, with the guide's own labels.

  1. Blue · rows kept is where the 206,880 kept rows sit on the rim, 2% of rows read. The wheel rounds to a whole percent above 1%; the exact share is 1.7%. It is one stretch because placed_at follows the order rows were written.
  2. Rim · the table is every page of orders in physical order, 183,000 of them.
  3. Arrow · full scan means the scan read all of them to reach the blue. An index on channel, placed_at would enter the rim at the blue stretch and read little else.

In the flow, the rows the filter removed drain off below the band as −11.8M. The finding reads Seq Scan on orders, keeps 2% · 36% of time, with Test index on PostgreSQL. On an engine without hypothetical indexes the card offers Copy CREATE INDEX instead.

Caveats

  • A Seq Scan of a table that sits in shared_buffers is cheap. 181,796 of the 183,000 pages of orders came from disk, which is why this one hurt.
  • The Plan tab numbers come from a 12-million-row copy. Yours will be smaller.

Last checked against Seqora 0.3.2, PostgreSQL 17 on .

Found a bug, missing a feature, or just curious? Write to me. Every email lands in my own inbox, not a ticket queue.