← 中文资源

简体中文版本

实现稳定的游标分页

用编码游标替代 offset 分页,在数据插入或删除时保持结果稳定,避免翻页时出现重复或遗漏记录。

阅读英文原文

offset 为什么会漂移

OFFSET 40 表示“跳过当前结果的前 40 行”。如果在当前位置之前插入一行,下一页会重复一行;如果删除一行,就会跳过一行。游标把下一页锚定在客户端实际看到的最后一行。

SELECT id, title, created_at
FROM posts
WHERE (created_at, id) < ($1, $2)
ORDER BY created_at DESC, id DESC
LIMIT $3;

步骤

  1. 定义确定性排序:ORDER BY created_at DESC, id DESC,用唯一字段作为并列时的决胜键。
  2. 多取一行判断是否还有下一页,用最后一行的排序键构造不透明游标。
  3. 将 (created_at, id) 编码为 base64url JSON,服务端校验结构,非法游标返回 400。
  4. 在翻页之间插入和删除记录,验证结果不重复、不遗漏。

验证

  • 第一页返回 N 行和 nextCursor。
  • 插入新行后使用游标获取第二页,第一页的记录不重复。
  • 删除第一页中的一行后获取第二页,没有记录被跳过。
  • 非法游标返回 400,而不是 500 或无界查询。
  • 服务端对 limit 设置上限。

权衡

游标分页在排序键有索引时稳定且高效,但无法低成本跳到第 50 页。只有在数据量小、静态或管理后台这类页码比稳定性更重要的场景,才使用 offset 分页。游标应视为不透明字符串,不要让客户端通过修改它构造任意 SQL 位置。