移动端必须用游标分页,因limit+offset在深分页时性能断崖下跌、数据易重复或丢失,且无法应对高并发写入;其核心要求是排序字段必须有索引、查询须用where cursor_field >/?
移动端下拉加载必须用游标分页,
Limit+Offset在滚动到深页时会卡顿、丢数据、重复,且无法应对高并发写入场景。为什么移动端不能用 page/size + Offset 分页
用户手指一滑可能直接跳到第 100 页,
Offset(9999)会让数据库扫描上万行再丢弃——MySQL 在OFFSET > 1000时性能断崖下跌;更致命的是,若列表中间插入新记录(比如别人刚发了一条动态),你下拉“加载更多”时会漏掉或重复某条。
- 常见错误现象:
db.Offset(5000).Limit(20)执行超 800ms,前端显示 loading 转圈超过 3 秒- 真实场景中,用户刷新后再次下拉,最后一条 ID 是
12345,但服务端查WHERE id > 12345 ORDER BY id LIMIT 20却返回空——因为排序字段没索引,或用了created_at导致时间重复created_at单独做游标不可靠:同一毫秒插入多条,WHERE created_at > '2026-08-21 13:00:00'可能跳过部分记录游标分页必须满足的三个硬条件
游标分页不是换几个参数就行,底层逻辑和传统分页完全不同,缺一不可:
- 排序字段必须有索引:首选主键
id(自增或 UUID 均可),次选created_at, id组合索引(避免时间重复)- 查询必须用
WHERE cursor_field > ?(升序)或(降序),不能混用 <code>OFFSET- 前端传的不是
page,而是上一页最后一条的id或cursor字符串(如base64(id:created_at)),首次请求不带 cursor示例(升序加载):
// 首次请求(无 cursor) db.Order("id ASC").Limit(20).Find(&users) // 后续请求(带 last_id=12345) db.Where("id > ?", 12345).Order("id ASC").Limit(20).Find(&users)如何安全地支持“上拉刷新”和“下拉加载更多”双向游标
纯升序游标只能向下翻,但移动端需要向上刷新最新数据(比如朋友圈新发帖)。这时得用双游标逻辑,且必须区分方向:
- 下拉加载更多(向旧数据走):用
id ,前端传 <code>last_id,查比它小的前 20 条- 上拉刷新(向新数据走):用
id > ? ORDER BY id ASC,前端传first_id,查比它大的前 20 条- 关键细节:两次查询的
ORDER BY方向必须和WHERE条件匹配,否则结果错乱。例如WHERE id > 12345 ORDER BY id DESC会返回最大 ID 开始倒排,不是你要的“最新 20 条”- 别在游标查询里用
Preload:先查出 ID 列表(SELECT id FROM ...),再用IN批量查关联数据,否则游标值丢失、N+1 查询爆炸Count 总数在游标分页里基本没意义,别查
游标分页本质是“流式读取”,总数无法高效获取——
SELECT COUNT(*)仍要扫全表,且结果对用户无实际价值(下拉加载不需要页码控件)。如果产品强依赖总数(如“共 1284 条”提示),只在首页首次加载时查一次缓存,后续滚动全部跳过Count。最容易被忽略的一点:游标分页的“稳定性”完全依赖排序字段的唯一性和索引覆盖。哪怕加了索引,如果业务允许软删除(
deleted_at不为空也参与排序),就可能让游标跳过有效数据——务必在WHERE条件里显式过滤deleted_at IS NULL,并把该条件加入联合索引。












