go分页需手动计算offset和limit,避免page=0或size=0导致全表扫描或panic;校验page≥1、size设上限;count()须与查询条件完全一致;响应结构应统一封装pageresult;深分页优先用游标而非offset。

分页查询必须自己算 offset 和 limit
Go 没有像 Laravel 或 Django 那样开箱即用的分页 ORM 方法,database/sql 和主流 ORM(如 GORM)只负责执行 SQL,分页逻辑得你来控制。常见错误是直接传入 page=0 或 size=0,结果查出全表或 panic——GORM 的 Limit(0) 会忽略限制,Offset(-1) 会报错。
实操建议:
-
page从 1 开始校验,小于 1 则默认为 1;size建议设上限(比如 100),防止恶意拉取全量 - 计算
offset = (page - 1) * size,别手抖写成page * size - GORM 中用
db.Offset(offset).Limit(size),不是Limit(size).Offset(offset)(顺序不影响结果,但可读性差易漏) - 如果用原生
sql.Rows,拼 SQL 时务必用参数化,别字符串拼接OFFSET ? LIMIT ?
GORM 分页总数不能靠 Count() 直接复用条件
很多人写 db.Where(...).Count(&total) 再写一遍 db.Where(...).Offset().Limit().Find(&list),看似合理,但条件稍有不一致(比如漏了 Where("deleted_at IS NULL")),总数和列表就对不上。更糟的是,Count() 在大数据表上可能慢到超时,尤其带 JOIN 或复杂 WHERE。
实操建议:
- 确保计数和查询用完全相同的
*gorm.DB实例(同个Where、Joins、Scopes链) - 对高频分页接口,考虑用估算总数(如 PostgreSQL 的
pg_class.reltuples)或缓存total(更新不频繁时) - GORM v2 起支持
Session复用,可先db.Session(&gorm.Session{NewDB: true}).Count()避免污染原 db 实例 - 如果允许“无精确总数”,可用
LIMIT + 1判断是否有下一页:查size+1条,返回前size条,多出那条仅作标志
封装分页响应结构要分离「数据」和「元信息」
直接把 total、page、size 塞进业务 struct(比如 User)会导致序列化混乱,前端也难统一处理。更麻烦的是,不同接口的分页字段名不一致(page_num vs current_page),后期改起来全是坑。
实操建议:
- 定义统一响应结构,例如:
type PageResult[T any] struct { Data []T `json:"data"` Total int `json:"total"` Page int `json:"page"` Size int `json:"size"` TotalPages int `json:"total_pages"` } -
TotalPages算法用(total + size - 1) / size,整除向上取整,别用math.Ceil(float64(total)/float64(size))(引入 float 可能精度丢失) - 不要在 handler 里手动 new 这个结构,抽成函数:
func Paginate[T any](list []T, total, page, size int) PageResult[T] - 如果某些接口不需要
TotalPages,就别硬塞,字段设为指针或加 tagjson:",omitempty"
MySQL OFFSET 深分页性能崩了怎么办
OFFSET 1000000 LIMIT 20 在 MySQL 上会扫描前 100 万行再丢弃,响应从几毫秒飙到几秒。这不是 Go 层能优化的,但封装时得留出绕过方案。
实操建议:
- 强制要求前端传上一页最后一条记录的排序字段值(比如
last_id=12345),改用WHERE id > 12345 ORDER BY id LIMIT 20 - GORM 支持
Where("id > ?", lastID).Order("id ASC").Limit(20),比 offset 快一个数量级 - 如果必须用页码,且数据变动少,可预生成分页映射表(如
page_index记录每页首条 ID),但增加运维成本 - 在封装的分页函数里加个
useCursor bool参数,让调用方显式选择策略,而不是默认走 offset
分页看着简单,真正上线后卡顿、总数不准、越界 panic 这些问题,八成出在 offset 计算、count 条件遗漏、或者响应结构没隔离清楚——盯住这三个点,比堆花哨泛型重要得多。
golang免费学习笔记(深入):立即使用
在学习笔记中,你将探索golang的核心概念和高级技巧!











