gorm 官方推荐手写 limit + offset 分页,需校验 page≥1 和 pagesize>0,并单独执行 count 查询;避免 preload 关联分页、慎用第三方 pagination 插件,高偏移场景应改用游标分页。

GORM 本身不提供开箱即用的分页函数,Paginate 不是 GORM 内置方法——这是最常被误解的一点。你需要自己组合 LIMIT 和 OFFSET,或借助社区封装(如 gorm/pagination),但后者有兼容性风险。
用 Limit + Offset 手写分页最稳妥
这是 GORM 官方推荐、零依赖、全版本兼容的做法。核心逻辑就是:第 page 页,每页 pageSize 条 → Offset((page - 1) * pageSize)。
- 注意
page必须 ≥ 1,否则Offset传负数会 panic -
Limit(0)在某些数据库(如 SQLite)下可能返回全部数据,务必校验pageSize > 0 - 总记录数需单独查一次:
db.Model(&User{}).Count(&total),不能复用带Limit的查询结果
// 示例:获取第 2 页,每页 10 条
var users []User
var total int64
page, pageSize := 2, 10
<p>db.Model(&User{}).Count(&total)
db.Offset((page - 1) * pageSize).Limit(pageSize).Find(&users)
</p>
用 gorm/pagination 插件要小心版本匹配
这个插件提供了 Paginate 方法,但它是第三方扩展,不是 GORM 官方维护。v2.2.0+ 的 GORM 对 Scope 行为做了调整,老版本插件会失效或漏数据。
- 确认你用的插件版本支持当前 GORM 版本(如
github.com/gookit/goutil@v2.4.0的分页模块已弃用) - 插件内部仍靠
Limit/Offset实现,但会自动帮你算Offset,容易掩盖边界错误(比如 page=0) - 它默认把
Count查询和主查询合并进一个事务?不,它仍是两次查询,性能没优化
避免在分页时用 Preload 加载一对多关联
如果你在分页查询中写了 Preload("Orders"),GORM 会先查出 10 个用户,再为每个用户发一条 SELECT ... WHERE user_id IN (?) —— 这看似合理,但实际会加载远超 10 条的订单数据,且无法限制“每个用户只取最新 3 个订单”这类需求。
- 分页场景下,优先考虑用
Joins+Group或子查询做聚合,而不是Preload - 真要关联数据,建议分两步:先分页查主表 ID 列表,再用
WHERE id IN (?)查关联数据并手动组装 -
Preload的Limit参数(如Preload("Orders", db.Limit(3)))在 v2.6.0 前不生效,别信文档旧示例
MySQL 和 PostgreSQL 分页偏移量性能差异大
OFFSET 100000 在 MySQL 上可能慢到超时,PostgreSQL 相对好些,但两者都随偏移量线性变慢。这不是 GORM 的问题,而是 SQL 本身局限。
- 线上高偏移分页必须改用游标分页(cursor-based pagination):用上一页最后一条记录的
id或created_at作为下一页条件 - GORM 支持游标写法:
db.Where("id > ?", lastID).Order("id ASC").Limit(20).Find(&users) - 别试图用
SELECT * FROM (SELECT ..., ROW_NUMBER() OVER(...)) t WHERE row_num BETWEEN x AND y—— GORM 不生成这种 SQL,手写原生 SQL 又失去 ORM 优势
分页真正的难点从来不在怎么写那两行 Limit 和 Offset,而在于你是否意识到:count 查询可能锁表、关联加载会放大结果集、大 offset 会拖垮数据库、前端传来的 page 参数根本不可信。这些没法靠一个封装函数解决。
golang免费学习笔记(深入):立即使用
在学习笔记中,你将探索golang的核心概念和高级技巧!











