← Home ← Codex ← DEBT
Browse by Category
+ added · updated 7d
← Back to glossary

MySQL LIMIT and OFFSET Pagination

Database Intermediate
debt(d7/e5/b5/t7)
d7 Detectability Operational debt — how invisible misuse is to your safety net

Closest to 'only careful code review or runtime testing' (d7). The detection_hints field notes 'automated: no' and only a code_pattern heuristic (LIMIT ? OFFSET ? with large offset values). No standard linter or SAST tool from the term's metadata flags this; it typically surfaces only during load testing or when users on deep pages report slowness in production.

e5 Effort Remediation debt — work required to fix once spotted

Closest to 'touches multiple files / significant refactor in one component' (e5). The quick_fix describes switching from LIMIT/OFFSET to keyset pagination (WHERE id > :last_id), which requires changing query logic, likely updating API contracts (replacing page numbers with cursors), and potentially modifying frontend consumers. This is more than a single-line patch but less than a full cross-cutting architectural rework.

b5 Burden Structural debt — long-term weight of choosing wrong

Closest to 'persistent productivity tax' (b5). Applies to web and CLI contexts broadly. Choosing OFFSET pagination for user-facing APIs shapes how data is fetched, how APIs expose pagination parameters, and how total-count queries are written. It doesn't define the entire system architecture but it imposes a recurring cost on any feature involving large dataset traversal.

t7 Trap Cognitive debt — how counter-intuitive correct behaviour is

Closest to 'serious trap — contradicts how a similar concept works elsewhere' (t7). The misconception field states explicitly that developers believe OFFSET pagination is fine for large datasets, when in reality it degrades linearly with offset depth. This is a well-documented gotcha but one that contradicts the intuitive expectation that page 1 and page 10,000 should have similar response times, making it a serious cognitive trap.

About DEBT scoring →

Also Known As

MySQL pagination LIMIT OFFSET cursor pagination keyset pagination

TL;DR

LIMIT restricts result row count; OFFSET skips rows for pagination — but OFFSET-based pagination degrades on large tables.

Explanation

LIMIT n returns the first n rows. LIMIT n OFFSET m skips m rows first. The problem: OFFSET requires MySQL to scan and discard m rows before returning results — OFFSET 10000 scans 10,000 rows. Keyset pagination (cursor-based) avoids this: WHERE id > last_seen_id ORDER BY id LIMIT n. Keyset pagination is O(log n) via the index rather than O(n) for OFFSET.

Common Misconception

OFFSET pagination is fine for large datasets. It gets linearly slower as the offset increases — page 1 and page 10,000 have vastly different response times.

Why It Matters

Page 500 of an OFFSET-paginated 1M-row table scans 5,000 rows to return 10. Keyset pagination returns the same page in a few milliseconds via an index lookup.

Common Mistakes

  • Using OFFSET-based pagination on user-facing APIs without a page count limit.
  • Not using ORDER BY with LIMIT — results are non-deterministic without explicit ordering.
  • Calculating total pages with SELECT COUNT(*) on large tables — can be slow; use approximate counts.

Avoid When

  • Do not use large OFFSET values on production tables — OFFSET 10000 scans 10,000 rows before returning results.
  • Never use LIMIT without ORDER BY — results are non-deterministic.

When To Use

  • Use LIMIT/OFFSET for shallow pagination (first few pages) where performance is acceptable.
  • Use keyset/cursor pagination (WHERE id > :last_id) for deep pagination on large tables.

Code Examples

✗ Vulnerable
// OFFSET pagination — slow on large tables
$offset = ($page - 1) * $perPage; // page 1000 scans 10,000 rows
$stmt = $pdo->prepare('SELECT * FROM users ORDER BY id LIMIT ? OFFSET ?');
$stmt->execute([$perPage, $offset]);
✓ Fixed
// Keyset pagination — consistent performance at any page
$stmt = $pdo->prepare(
    'SELECT id, email FROM users WHERE id > ? ORDER BY id LIMIT ?'
);
$stmt->execute([$lastSeenId, $pageSize]);
$rows = $stmt->fetchAll();
$nextCursor = end($rows)['id'] ?? null;

Added 31 Mar 2026
Views 84
Rate this term
No ratings yet
🤖 AI Guestbook educational data only
| |
Last 30 days
1 ping T 0 pings W 1 ping T 2 pings F 0 pings S 0 pings S 2 pings M 0 pings T 0 pings W 0 pings T 1 ping F 4 pings S 1 ping S 1 ping M 2 pings T 0 pings W 0 pings T 1 ping F 0 pings S 0 pings S 0 pings M 1 ping T 0 pings W 0 pings T 0 pings F 0 pings S 2 pings S 0 pings M 0 pings T 0 pings W
No pings yet today
No pings yesterday
ChatGPT 41 Google 8 Scrapy 5 Perplexity 4 Ahrefs 3 Unknown AI 2 Claude 2 PetalBot 2 Meta AI 1 Sogou 1 Qwen 1 Majestic 1
crawler 65 crawler_json 6
DEV INTEL Tools & Severity
⚙ Fix effort: Medium
⚡ Quick Fix
For deep pagination, switch from LIMIT/OFFSET to keyset pagination using WHERE id > :last_id ORDER BY id LIMIT :page_size
📦 Applies To
web cli
🔗 Prerequisites
🔍 Detection Hints
LIMIT ? OFFSET ? with large offset values
Auto-detectable: ✗ No
⚠ Related Problems
🤖 AI Agent
Confidence: Medium False Positives: Medium ✗ Manual fix Fix: Medium Context: Line Tests: Update


✓ schema.org compliant