应采用游标分页替代limit+offset:用created_at和id组合游标续读,建复合索引(idx_eval_cursor),主查询延迟加载多媒体字段,后台管理页需限页、缓存总数、单列排序索引。

直接用 Limit + Offset 做电商评价的混合分页(含图片、视频、文字)极易翻车——数据重复、漏显、响应超时三连击,尤其当用户刷到第 30 页后开始疯狂发图时。
为什么评价系统不能只靠 page 和 pagesize?
电商评价天然带「高写入+多类型+强排序依赖」:新图上传、视频转码完成、用户点赞触发状态更新,都会让同一页的物理行位置漂移。MySQL 执行 OFFSET 290 LIMIT 10 时,实际要扫描前 300 行再丢弃,而其中可能已有 5 条新插入的带图评价挤进了中间位置。
- 常见错误现象:
page=30&size=10返回空数组,但page=29和page=31都能返回内容;某条带视频的评价在两页里重复出现 - 混合内容加重问题:一张缩略图字段(
thumbnail_url)和一段视频地址(video_url)共存于同一张表,WHERE条件变复杂,索引覆盖难度上升 - 排序必须锁定:没加
Order("created_at DESC, id DESC")的查询,MySQL 可能按任意顺序返回,分页结果不可复现
怎么安全地查带图/带视频的评价?
核心是把「页码跳转」换成「游标续读」:不传 page,改传上一页最后一条评价的 created_at 和 id 组合值(例如 ?cursor=2026-08-20T14:22:11Z_12345),后端据此构造 WHERE (created_at, id) 查询。
- 必须建复合索引:
CREATE INDEX idx_eval_cursor ON evaluations (created_at DESC, id DESC);,否则游标查询退化为全表扫描 - 前端首次请求不带
cursor,后端用ORDER BY created_at DESC, id DESC LIMIT 11查 11 条(多取 1 条用于生成下一页 cursor),返回时附带next_cursor - 对多媒体字段做延迟加载:主查询只查
id, user_id, content, created_at, has_image, has_video,图片 URL 和视频地址用单独接口或按需 JOIN,避免大字段拖慢分页主链路 - GORM 写法示例:
var evals []Evaluation err := db.Where("status = ?", "approved"). Where("(created_at, id)
如果非要支持 page 参数(比如后台管理)怎么办?
仅限低频、小数据量、人工可控场景,且必须加三道保险:
- 强制
page上限:比如if p.Page > 50 { p.Page = 50 },超过 50 页一律重定向到游标分页入口 - 总数查询独立执行:
db.Model(&Evaluation{}).Where("status = ?", "approved").Count(&total),绝不能和分页查询共用同一个db实例链式调用 - 缓存总页数:对
total值加 Redis 缓存(如eval:total:approved,过期时间 60 秒),避免每次请求都扫全表 - 排序字段必须有单列索引:
CREATE INDEX idx_eval_created ON evaluations (created_at DESC);,否则OFFSET性能断崖出现在OFFSET 5000左右
真正难的不是写对 Limit 和 Offset,而是判断什么时候该停手——当评价表日增 5w+ 条、带图率超 60%、用户平均滚动深度达 42 页时,游标分页不是优化项,是保命线。











