★ 分页查询:Limit/Page 与 AllAndCount/ScanAndCount
面试问法:GoFrame 分页列表怎么做?怎么同时拿到列表和总数? 一句话结论:ORM 提供
Limit(offset, size)与Page(page, size)两种分页;官方从 v2.5 起提供AllAndCount/ScanAndCount,一条链同时完成“分页查询 + COUNT”,省去先 Count 再查列表的样板。配套规范路由在 Req/Res 里定义 page/size 与 list/total 即可。
方法速查
| 方法 | 作用 | 说明 |
|---|---|---|
Limit(offset, size) | 经典偏移分页 | 需自己算 offset |
Page(page, size) | 页码分页 | 内部换算 offset |
Count() | 只数总数 | 老写法要另起一条链 |
AllAndCount() | 查列表 Result + 总数 | v2.5+,返回 (result, total, err) |
ScanAndCount(&list,&total,flag) | 扫描进结构体 + 总数 | v2.5+,列表与总数一步到位 |
规范路由下的标准写法
type UserListReq struct {
g.Meta `path:"/user" method:"get" tags:"User" summary:"user list"`
Page int `d:"1" v:"min:1" dc:"页码"`
Size int `d:"10" v:"between:1,100" dc:"每页数量"`
Status int `dc:"状态过滤,0 不过滤"`
}
type UserListRes struct {
List []*entity.User `json:"list"`
Total int `json:"total"`
Page int `json:"page"`
Size int `json:"size"`
}
func List(ctx context.Context, in *v1.UserListReq) (out *v1.UserListRes, err error) {
m := dao.User.Ctx(ctx)
if in.Status > 0 {
m = m.Where("status", in.Status)
}
out = &v1.UserListRes{Page: in.Page, Size: in.Size}
// 第三参 false:总数用 COUNT(1);true 则按指定字段 count
err = m.Page(in.Page, in.Size).OrderDesc("id").ScanAndCount(&out.List, &out.Total, false)
return
}
老版本/特殊场景等价写法:先 total, err := m.Count(),再 m.Page(...).OrderDesc("id").Scan(&out.List)。
注意点
- Count 不受 Limit/Page 影响,这是 ScanAndCount/AllAndCount 的既定行为。
- 带 GROUP BY/子查询时 count 可能不等于行数,需单独设计计数逻辑。
- 服务端封顶 size(如 between:1,100),防止超大分页扫全表。
- 排序字段做白名单:自定义排序别把任意列名拼进 Order,默认主键倒序最稳。
常见追问
- 追问:深分页性能差怎么办?——offset 越大扫描越多;大结果集改用游标/键集分页(where id < 游标值 order by id desc limit size)。
- 追问:ScanAndCount 会比两条查询更快吗?——不会减少次数(仍是一次列表 + 一次 COUNT),省的是样板与出错概率,优化点应放在索引与避免 N+1。
记忆点
- 一条链收尾:
Page + OrderDesc + ScanAndCount(&list,&total,false);语义与版本要求以官方文档为准。