OFFSET 分页为什么越来越慢
比较 OFFSET 与游标分页的扫描成本、稳定排序和数据变化下的翻页结果。
问题与判断方法
LIMIT 加 OFFSET 适合数据量较小、需要直接跳到指定页码的后台列表。它的优势是实现简单、页码直观;但页码越深,数据库通常需要定位并丢弃越多前置记录,响应时间会随偏移量增长。
游标分页也叫 keyset pagination。它不记录“第几页”,而是记录上一页最后一行的排序键,再用 WHERE 从该位置之后继续读取。以主键升序为例,条件是 id > last_id,索引可以直接定位新的起点。
排序字段不唯一时,游标必须携带完整排序键。例如 ORDER BY created_at DESC, id DESC 对应的下一页条件要同时比较 created_at 与 id;只记录时间会在同一秒有多行时造成重复或遗漏。
数据变化也会影响选择。OFFSET 按位置翻页,前面插入或删除一行会推动后续位置;游标围绕已见过的最后一个键继续,通常更适合信息流和连续浏览,但它不方便随机跳到第 500 页。
上线前应针对真实索引和目标页深度比较 EXPLAIN ANALYZE,并明确接口是否需要总页数、任意跳页、正反向翻页以及排序字段是否可变。不要只根据四行演示数据推断生产性能。
示例
示例数据表
表名:
orders| id | customer_id | product_id | name | department | amount | status | created_at | discount |
|---|---|---|---|---|---|---|---|---|
| 1 | 101 | 201 | Alice | Sales | 1200 | paid | 2026-01-03 | 0 |
| 2 | 102 | 202 | Bob | IT | 900 | pending | 2026-01-08 | 50 |
| 3 | 101 | 201 | Cindy | Sales | 1500 | paid | 2026-02-02 | 100 |
| 4 | 104 | 203 | David | HR | 700 | cancelled | 2026-02-15 | 0 |
优化前 / 不推荐
SELECT * FROM orders ORDER BY id LIMIT 2 OFFSET 2;优化前运行结果
| id | customer_id | product_id | name | department | amount | status | created_at | discount |
|---|---|---|---|---|---|---|---|---|
| 3 | 101 | 201 | Cindy | Sales | 1500 | paid | 2026-02-02 | 100 |
| 4 | 104 | 203 | David | HR | 700 | cancelled | 2026-02-15 | 0 |
优化后 / 推荐
SELECT * FROM orders WHERE id > 2 ORDER BY id LIMIT 2;优化后运行结果
| id | customer_id | product_id | name | department | amount | status | created_at | discount |
|---|---|---|---|---|---|---|---|---|
| 3 | 101 | 201 | Cindy | Sales | 1500 | paid | 2026-02-02 | 100 |
| 4 | 104 | 203 | David | HR | 700 | cancelled | 2026-02-15 | 0 |
为什么这样更好:样例结果相同,但机制不同:OFFSET 先越过两行,游标条件以主键 2 为已知位置开始读取;偏移量增大时差异才会放大。
这篇内容是否清楚?
感谢反馈,我们会把它用于后续内容复核。