I want to implement cursor-based pagination for a large data set.
With OFFSET
based pagination, when the user wants page N
, you just OFFSET
N * page_size
.
The query ends up like:
SELECT *
FROM books
ORDER BY id
OFFSET 100000
LIMIT 10;
But OFFSET
gets slower the larger the value it's given because PostgreSQL has to load and discard the preceding rows.
A cursor-based approach is where we tell the user "here's page N
, and since the last record on it has id
of X
, to get the next page you should ask me for records with id > X
."
The query ends up like:
SELECT *
FROM books
WHERE id > 100000
ORDER BY id
LIMIT 10;
In that case, PostgreSQL can load only the needed rows.
That works great when sorting by id
.
But I'd like to be able to sort by other columns - for instance, by title
- and still paginate.
The problem is that title
is not unique. So if the final record on page N
has the title "About Weasels" and there are multiple books with that title, requesting the next page with WHERE title > 'About Weasels'
may skip some of them.
I can get unique values by having the user request WHERE (title, id) > ('About Weasels', 100000)
, but that performs poorly because PostgreSQL has to compute (title, id)
for every row.
I tried adding an index to pre-compute that 2-tuple: CREATE INDEX books_title_and_id ON books (title, id);
...but that index makes no difference to the query plan.
Is there an index I could create which would speed up this query?
CREATE INDEX books_title_and_id ON books ((title || ' ' || id));
andSELECT * FROM books WHERE ((title || ' ' || id)) > (('About Weasels' || ' ' || 100000000)) ORDER BY ((title || ' ' || id)) LIMIT 1;
is fast. But it's ugly IMO.order by title, id