分页查询必须传 page 和 size 并校验:page

分页查询必须传 page 和 size,不能只靠前端传偏移量
后端自己算 offset 容易出错,尤其当 page 为 0 或负数、size 超过上限时,数据库可能直接报错或返回空结果。Golang 中应统一校验并转换:
-
page默认为 1,小于 1 则设为 1 -
size应限制范围(如 1–100),超出则截断或返回错误 - 用
offset = (page - 1) * size计算,避免前端恶意传大偏移量导致慢查询
示例片段:
if page < 1 {
page = 1
}
if size < 1 || size > 100 {
size = 20
}
offset := (page - 1) * size用 sql.DB.Query 做分页时,务必加 ORDER BY
MySQL / PostgreSQL 等对无序分页不保证稳定性:同一查询多次执行可能因索引顺序、MVCC 版本不同而跳过或重复记录。尤其在高并发写入场景下,漏数据很常见。
- 没有
ORDER BY的LIMIT OFFSET不是真正分页,只是“取前 N 条” - 推荐用主键或唯一时间字段排序,例如
ORDER BY id ASC或ORDER BY created_at DESC, id DESC - 若业务允许,优先考虑游标分页(
WHERE created_at ),避免OFFSET性能衰减
用 gorm 时,Limit/Offset 不会自动处理总数,需手动 COUNT(*)
gorm 的 Limit + Offset 只负责查数据,不附带总条数。但多数分页 UI 需要显示“共 XX 条”“第 X 页”。别用 db.Count(&total) 直接套在带 WHERE 的链式查询上——它会忽略 Order、Joins 甚至部分 Where 条件。
立即学习“go语言免费学习笔记(深入)”;
- 正确做法:拆成两个查询,或用子查询封装条件
- 简单场景可用
db.Session(&gorm.Session{NewDB: true}).Model(&User{}).Where(...).Count(&total)防止复用原 query 的 limit/offset - 复杂关联查询建议手写
SELECT COUNT(*) FROM (SELECT ... ) AS t,用Raw执行,更可控
大数据量下 OFFSET 分页变慢?换游标或覆盖索引
当 OFFSET 超过 10 万行,MySQL 可能扫描并丢弃大量数据,响应从毫秒升到秒级。这不是 Golang 层能优化的,得从 SQL 和索引入手。
- 游标分页要求前端传上一页最后一条的排序字段值(如
last_created_at),SQL 改为WHERE created_at - 确保排序字段有索引;若用多字段排序(如
ORDER BY status, created_at),索引必须包含全部字段且顺序一致 - 避免
SELECT *,只查必要字段,减少网络和内存开销;大文本字段(如TEXT)可单独异步加载
游标分页不能跳页,但对“下一页”“上一页”场景足够高效,也规避了 OFFSET 的偏移漂移问题。









