gorm无原生paginate方法,需手动组合where、order、limit、offset,严格校验page≥1、pagesize∈[1,100],显式order排序,count查询须独立db实例隔离,大偏移量应改用游标分页。

GORM 没有 Paginate 方法,所谓“动态条件分页”不是靠封装函数自动完成的,而是你手动组合 Where、Order、Limit、Offset 四个动作,并确保每一步都不被绕过或复用错上下文。
参数解析必须先校验再转换
前端传 page=abc&page_size=0 是常态,不能等 strconv.Atoi panic 了才处理。Gin 的 c.ShouldBindQuery(&p) 能统一拦截类型错误,但结构体字段仍需二次兜底:
-
PageNum小于等于 0 时强制设为1,别返回 400 —— 用户点错页码不是服务端该拒掉的理由 -
PageSize必须限制在1–100区间,超出就截断(如设为20),防恶意刷库或慢查询拖垮 DB - 空字符串、非数字值在
ShouldBindQuery阶段就会失败,此时应直接c.JSON(400, ...),不 fallback 到默认值
动态 Where 条件要链式拼接,不能复用 db 实例
带搜索关键词或状态筛选的分页,容易把 Where 写成固定字符串拼接,结果 SQL 注入或类型不匹配。正确做法是用 GORM 的链式构建:
- 从干净的
db.Model(&User{})开始,每加一个条件都返回新*gorm.DB,比如db.Where("status = ?", p.Status) - 字符串模糊搜用
LIKE:db.Where("name LIKE ?", "%"+p.Keyword+"%"),别手写"name LIKE '%" + p.Keyword + "%" - 多个可选条件用
if p.Keyword != ""包裹,避免生成WHERE name LIKE '%%'这种无意义条件 - 别在同一个
*gorm.DB实例上反复调Where后又去Count—— 条件会残留,总数查不准
Count 查询必须隔离会话或手写子查询
db.Where(...).Limit(20).Offset(40).Count(&total) 返回的 total 永远 ≤ 20,因为 Count 继承了前面的 Limit 和 Offset。这是线上最常踩的坑:
- 简单场景用
db.Session(&gorm.Session{NewDB: true}).Model(&User{}).Where(...).Count(&total),靠NewDB: true隔离上下文 - 复杂 JOIN 或 Preload 场景,
Count容易出错,直接手写子查询更稳:db.Raw("SELECT COUNT(*) FROM (SELECT 1 FROM users u LEFT JOIN profiles p ON u.id = p.user_id WHERE ...) AS t").Scan(&total) - 如果业务允许(比如列表页不显示总页数),可只查
pageSize + 1条,用len(results) > pageSize判断是否有下一页,省掉COUNT
ORDER BY 不是可选项,而是分页稳定性的前提
没 Order 的分页,翻页时漏数据或重复是必然的,尤其在有并发写入的生产环境:
- 必须显式写
.Order("id ASC")或.Order("created_at DESC, id DESC"),二级排序防时间重复 - 排序字段必须有索引,否则 MySQL/PostgreSQL 会全表扫描+文件排序,OFFSET 越大越慢
- 别用
.Order("RAND()")做“随机分页”,它无法翻页且性能爆炸 - 游标分页(
WHERE id > ? ORDER BY id LIMIT 20)虽高效,但要求前端传上一页最后一条的id,不适合传统页码控件
真正难的不是写对一行 Offset((p.PageNum-1)*p.PageSize),而是保证 Where 条件、Order 字段、Count 查询三者完全对齐,且不因链式调用隐式污染。多数线上分页 bug,都出在以为“同一个 db 变量能复用到底”。
golang免费学习笔记(深入):立即使用
在学习笔记中,你将探索golang的核心概念和高级技巧!











