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;
步骤
- 定义确定性排序:
ORDER BY created_at DESC, id DESC,用唯一字段作为并列时的决胜键。 - 多取一行判断是否还有下一页,用最后一行的排序键构造不透明游标。
- 将
(created_at, id)编码为 base64url JSON,服务端校验结构,非法游标返回 400。 - 在翻页之间插入和删除记录,验证结果不重复、不遗漏。
验证
- 第一页返回 N 行和 nextCursor。
- 插入新行后使用游标获取第二页,第一页的记录不重复。
- 删除第一页中的一行后获取第二页,没有记录被跳过。
- 非法游标返回 400,而不是 500 或无界查询。
- 服务端对 limit 设置上限。
权衡
游标分页在排序键有索引时稳定且高效,但无法低成本跳到第 50 页。只有在数据量小、静态或管理后台这类页码比稳定性更重要的场景,才使用 offset 分页。游标应视为不透明字符串,不要让客户端通过修改它构造任意 SQL 位置。