分页查询需同时获取数据和总数,不能仅用Limit/Offset;必须校验page/pageSize、复用查询条件统计总数,并用Session克隆DB避免干扰。

分页查询在 Gin + GORM 组合里不是开箱即用的功能,GORM 本身不提供 Paginate 方法,必须手动计算 offset 和 limit,且容易漏掉边界校验或总数统计逻辑。
为什么不能直接用 Limit() 和 Offset() 就完事?
单纯拼 Limit() 和 Offset() 只能拿到当前页数据,但前端通常还需要总条数(total)来渲染页码。GORM 的 Count() 需要复用相同查询条件,否则总数和列表对不上;另外,page 和 pageSize 参数没做校验时,可能触发负数 offset 或超大 limit,导致 SQL 错误或性能问题。
-
page应默认为1,小于 1 时强制设为1 -
pageSize建议限制上限(如100),防止恶意请求拖垮数据库 - 总数统计必须和主查询共用
Where、Joins、Scopes等条件,不能单独写一个Count()
怎么安全地构造带总数的分页响应?
推荐封装一个通用函数,接收 *gorm.DB 查询对象、页码、每页数量,返回数据切片、总数、错误。关键点是用 Session(&gorm.Session{NewDB: true}) 克隆 DB 实例分别跑 count 和 list,避免相互干扰。
func Paginate(db *gorm.DB, page, pageSize int, result interface{}) (int64, error) {
offset := (page - 1) * pageSize
if offset < 0 {
offset = 0
}
if pageSize <= 0 {
pageSize = 10
}
if pageSize > 100 {
pageSize = 100
}
<pre class='brush:php;toolbar:false;'>// 克隆 db 用于 count,避免被 limit/offset 影响
var total int64
db.Session(&gorm.Session{NewDB: true}).Count(&total).Error
return total, db.Offset(offset).Limit(pageSize).Find(result).Error}
立即学习“go语言免费学习笔记(深入)”;
调用时注意:result 必须是指针(如 &users),且类型要和模型匹配;db 应已包含所有过滤条件(比如 Where("status = ?", 1))。
Gin 路由里怎么接参并返回标准 JSON?
Gin 的 c.Query 返回字符串,必须显式转成整型,失败时要给默认值。响应结构建议统一字段名,比如 data、total、page、pageSize,别用 list 或 items 这种不一致的键。
- 用
strconv.Atoi(c.DefaultQuery("page", "1"))获取页码,默认1 - 用
strconv.Atoi(c.DefaultQuery("pageSize", "10"))获取单页数量,默认10 - 总数和数据查完后,直接
c.JSON(200, map[string]interface{}{...})返回,不要套多层 struct
示例片段:
users := []User{}
total, err := Paginate(db.Where("deleted_at IS NULL"), page, pageSize, &users)
if err != nil {
c.JSON(500, gin.H{"error": "query failed"})
return
}
c.JSON(200, gin.H{
"data": users,
"total": total,
"page": page,
"pageSize": pageSize,
})真正麻烦的是关联查询分页——比如查用户及其订单,Joins("Orders") 后 Count() 会因笛卡尔积虚高,必须用子查询或 Distinct() 修正。这个坑不提前意识到,上线后总数就一直错。


















