Your Order History Is Slow.
The Index Is Not the Query.
Explore why a SQL query that returns just 20 orders can still examine thousands of rows. Move the controls to compare access patterns—not invented benchmark timings.
01 · Follow the request
Indexes are chosen by a cost-based optimizer. An index that exists is not necessarily an index the planner will use.
02 · Reproduce a duplicated order
A shopper loads two orders, then another order arrives before they click Next. Compare the next page returned by OFFSET and a saved cursor.
03 · Interactive query-work simulator
Conceptual simulation only: this is a deterministic teaching model, not a real database, measured execution plan, or timing benchmark. It models skipped rows and a simplified access-path compatibility score.
For simplicity, the simulator assumes a full candidate set and 20-row pages; real plans depend on selectivity, statistics, data layout, caches, visibility and optimizer choices. Keyset still incurs work finding the cursor position, and a function expression may be supported by an expression index.
04 · The actual SQL fix
Try the query on PostgreSQL 15+ with the sample schema from the full blog. Notice the tie-breaker and exclusive cursor:
CREATE INDEX idx_orders_customer_created_id
ON orders (customer_id, created_at DESC, order_id DESC)
INCLUDE (status, total_amount);
SELECT order_id, created_at, total_amount
FROM orders
WHERE customer_id = 42
AND created_at >= TIMESTAMPTZ '2026-10-30 00:00+00'
AND created_at < TIMESTAMPTZ '2026-11-02 00:00+00'
AND (created_at, order_id) <
(TIMESTAMPTZ '2026-10-31 12:00+00', 1005)
ORDER BY created_at DESC, order_id DESC
LIMIT 3;| Order | Created UTC | Total |
|---|---|---|
| 1004 | Oct 31 08:30 | $240.00 |
| 1003 | Oct 30 10:00 | $19.50 |
| 1002 | Oct 30 10:00 | $129.00 |
05 · Practice challenges
Beginner — Return Oct 31 orders without DATE(created_at)
SELECT order_id FROM orders WHERE customer_id = 42 AND created_at >= TIMESTAMPTZ '2026-10-31 00:00+00' AND created_at < TIMESTAMPTZ '2026-11-01 00:00+00' ORDER BY created_at DESC, order_id DESC; -- Expected: 1005, 1004
Intermediate — Cursor after order 1006
SELECT order_id FROM orders WHERE customer_id = 42 AND (created_at, order_id) < (TIMESTAMPTZ '2026-11-01 06:00+00', 1006) ORDER BY created_at DESC, order_id DESC LIMIT 2; -- Expected: 1005, 1004
Architect — Why can an INCLUDE index still read heap pages?
MVCC visibility can require heap access. Validate `Heap Fetches`, buffer reads and actual plans. For a large production rollout, assess disk/WAL and use `CREATE INDEX CONCURRENTLY` when appropriate.
06 · Senior engineer quiz
Three original interview-style questions. Submit to see explanations for every answer.