Paginationmedium3-5 years

Why does offset pagination return duplicate or missing rows under concurrent writes, and how does keyset pagination avoid it?

LIMIT/OFFSET pagination counts rows to skip. If a row is inserted or deleted between two page requests, everything after it shifts position — a row that was at offset 3 is now at offset 2 (or 4), so "skip 3, take 3" skips a different set than it did a moment earlier, producing a duplicate on one page and a skipped row entirely. Keyset pagination names the last row seen (WHERE id < :lastSeenId ORDER BY id DESC LIMIT n) instead of counting — an insert above that row changes nothing about which rows satisfy the condition, so the next page is stable regardless of what else happened in between.

The lesson behind it →