Offset是跳过行数而非页码,需为(page-1)*page_size;page=0或非数字会导致panic或查全表;page_size=0在SQLite返回全部数据;应使用结构体校验参数并改用游标分页避免错位。

Offset 不是页码,是跳过行数
Offset 的值必须是 (page - 1) * page_size,不是 page 本身。比如第 5 页、每页 30 条,得写 Offset(120),写成 Offset(5) 就会只跳过前 5 行,结果完全错乱。
常见错误现象:
-
page = 0→Offset(-30)→ GORM panic(Go runtime 直接崩溃) -
page = "abc"→strconv.Atoi返回 0 →Offset(0)→ 查全表(SQLite 下尤其危险) -
page_size = 0→Limit(0)在 SQLite 返回全部数据,在 MySQL 可能报错或被优化器忽略
实操建议:
- 用结构体绑定参数:
type PageQuery struct { PageNum int `form:"page" binding:"gte=1,lte=1000"` PageSize int `form:"size" binding:"gte=1,lte=100"` } - 没传
page时,PageNum默认为 0 → 必须在绑定后手动校正:if p.PageNum - 别依赖“未传即默认”,Go 的 int 字段零值就是 0,而 0 是非法 Offset
Limit 和 Offset 必须配合 Order 使用
不加 Order 的 Limit+Offset 查询,结果不可复现。MySQL 和 PostgreSQL 都不保证无序查询的物理顺序一致,哪怕表静态、没并发写入,两次执行也可能返回不同记录。
典型翻页问题:
- 同一条记录在第 2 页和第 3 页重复出现
- 某条记录直接“消失”,前后页都查不到
- 刷新页面,当前页内容变动
实操建议:
- 禁止单用
Order("status ASC")—— status 值重复率高,数据库内部排序不稳定 - 慎用
Order("created_at DESC")—— 广告/日志类表毫秒级写入,时间戳极易重复 - 必须补唯一次级字段:如
Order("created_at DESC, id DESC")或Order("id ASC") - 所有排序字段必须有索引,否则
OFFSET 10000会触发全表扫描式跳行,性能断崖下跌
Count 总数不能和分页共用同一个 *gorm.DB 实例
db.Where("x = ?", v).Limit(10).Offset(20).Count(&total) 返回的 total 永远 ≤ 10 —— 这不是 bug,是 GORM v2 的设计逻辑:Count 会复用链式调用中已设置的 Limit 和 Offset。
实操建议:
- 拆成两个独立查询:
query := db.Where("x = ?", v),再分别调query.Count(&total)和query.Limit().Offset().Find(&list) - 涉及
Preload时更要小心:总数查询必须用db.Model(&User{}),避免 JOIN 放大导致总数虚高 - 软删除场景下,
Model()必须显式指定结构体,否则可能被全局Scope干扰(比如自动加WHERE deleted_at IS NULL) - 大数据量且不要求精确总页数时,可只查
page_size + 1条,用len(results) > page_size判断是否有下一页
联合主键或高频写入场景下,Limit+Offset 本质不可靠
按 (user_id, created_at) 这类组合字段分页时,一旦中间插入一条 user_id 相同但 created_at 更早的记录,后续页就会错位。GORM 的 Limit+Offset 无法感知这种动态变化,只能机械跳行。
广告、日志、监控等高频写入表,created_at 密集重复,单靠时间排序根本无法保证稳定性。
实操建议:
- 改用游标分页:基于上一页最后一条的完整排序键构造
WHERE (created_at, id) 条件 - 确保排序字段顺序与联合索引顺序严格一致,否则索引失效
- 游标值必须 URL-safe 编码(如 base64),不能直接透传二进制或特殊字符
- 前端分页控件若强依赖总页数,需接受“下一页存在与否”比“总共有几页”更真实
复杂点不在语法,而在边界控制——page 和 page_size 的校验必须在进入 GORM 链式调用前完成,Order 字段必须带索引且组合唯一,Count 必须脱离主查询上下文。漏掉其中任一环,上线后不是慢,而是错。


















