示例说明
OFFSET 十万意味着数据库要扫过并丢弃十万行才返回二十行——页码越深代价线性增长。键集分页把锚点写进 WHERE:复合元组比较直接在索引上定位起点,二十行即取即回,第一页与第一万页同价。元组比较语义等价于 OR 组合的两组条件但更简洁。
技术要点
- 元组比较替代 OR 组合更简洁
- 深页与首页同价不受页码影响
- 索引排序方向与查询严格一致
代码解读
第一条查询的 WHERE 元组比较等价于时间早于锚点或时间相同但 ID 小——复合索引按相同方向组织,B 树定位一次即得;对照的 OFFSET 版本演示代价陷阱。首页请求只是无 WHERE 的退化形态。末尾的 EXPLAIN ANALYZE 供实际对比两种计划的 rows 与执行时间。
使用建议
游标对客户端保持不透明(编码或加密)。排序键必须唯一否则分页会重漏行。前端无限滚动天然契合此模式,页码跳转需换方案。
代码示例
-- 键集分页:锚点条件让索引直接定位下一页
SELECT id, created_at, title
FROM articles
WHERE (created_at, id) < ('2026-10-21 14:30:00', 98765)
ORDER BY created_at DESC, id DESC
LIMIT 20;
-- 对照的 OFFSET 版本:深页要扫过全部前序行
SELECT id, created_at, title
FROM articles
ORDER BY created_at DESC, id DESC
LIMIT 20 OFFSET 100000;
-- 首页请求(无锚点)
SELECT id, created_at, title
FROM articles
ORDER BY created_at DESC, id DESC
LIMIT 20;
-- 配套复合索引:排序方向与查询一致
CREATE INDEX idx_articles_page
ON articles (created_at DESC, id DESC);
-- 验证两种方案的代价差异
EXPLAIN ANALYZE
SELECT id FROM articles
WHERE (created_at, id) < ('2026-10-21 14:30:00', 98765)
ORDER BY created_at DESC, id DESC LIMIT 20;