Offset分页越翻越慢是因为数据库必须真实扫描并丢弃前N行,而非跳过;游标分页通过where id > last_id避免扫描,性能稳定且需确保排序字段有索引、前端透传last_id而非页码。

Offset分页在GORM里为什么越翻越慢
因为MySQL/PostgreSQL执行OFFSET 100000时,必须先扫描并丢弃前100000行——不是跳过,是真读、真比较、真丢弃。数据量到百万级,第1000页可能耗时超1.8秒,且内存和IO压力陡增。
常见错误现象:db.Offset(99999).Limit(20)在压测中触发数据库CPU飙升、连接超时;同一页刷新两次,返回记录顺序不一致甚至漏掉某条。
- 必须加
Order("id ASC")或Order("created_at DESC, id DESC"),否则无序分页结果不可重现 -
Offset值不能由前端直接传入,要校验page >= 1且pageSize在1–100之间,避免恶意构造大偏移量 - 别在
Count()查询里复用带Limit/Offset的DB实例,它会忽略这些条件,导致总数不准
游标分页怎么写才不翻车
游标分页本质是“基于最后一条已知记录继续往后取”,绕开OFFSET的物理扫描缺陷。但写错一个细节,就可能返回空结果或重复数据。
关键点:last_id必须来自上一页最后一条记录的排序字段值,且该字段要有索引、单调递增(id最稳,created_at在高并发下需加id兜底)。
立即学习“go语言免费学习笔记(深入)”;
- 首次请求:用
db.Order("id ASC").Limit(20).Find(&users),不带Where - 后续请求:用
db.Where("id > ?", lastID).Order("id ASC").Limit(20).Find(&users) - 前端必须传
last_id(不是页码),后端不做转换,直接拼进Where;若last_id非法(如负数、非数字),应返回400而非静默处理 - 如果排序用
created_at DESC,则条件要改成created_at ,且注意时区和精度(建议用<code>datetime(3)或bigint时间戳)
GORM里Count总数怎么查才准又快
db.Model(&User{}).Count(&total)看着简单,但一加Where条件就容易出错:它不会继承主查询的Joins、Preload或Group逻辑,纯按模型表硬扫。
典型陷阱:你写了db.Joins("JOIN profiles ON users.id = profiles.user_id").Where("profiles.active", true)查用户,却用db.Model(&User{}).Where(...).Count()算总数——结果漏了JOIN条件,总数虚高。
- 简单场景:用
db.Session(&gorm.Session{NewDB: true}).Model(&User{}).Where(...).Count(&total)隔离会话,避免污染 - 复杂关联:手写子查询
db.Raw("SELECT COUNT(*) FROM (SELECT 1 FROM users u JOIN profiles p ON u.id = p.user_id WHERE p.active = ?) AS t", true).Scan(&total) - 大数据量慎返
total:可改用has_next布尔值替代,比如查Limit(21),若得21条则设has_next = true,否则false
Preload关联分页为什么数据量爆炸
在分页查询里写db.Preload("Orders"),GORM会先查10个用户,再为这10个ID发一条SELECT * FROM orders WHERE user_id IN (1,2,...,10)——但如果每个用户有上千订单,一次就拉回上万行,远超页面所需。
更糟的是:Preload("Orders", db.Limit(3))在GORM v2.6.0之前完全不生效,文档示例会误导你。
- 正确做法:先分页查主表ID列表
db.Select("id").Offset(...).Limit(...).Find(&ids),再用db.Where("user_id IN ?", ids).Order("created_at DESC").Limit(30).Find(&orders)查关联,手动组装 - 想单SQL搞定?用
Joins+ 窗口函数(PostgreSQL)或ROW_NUMBER()子查询(MySQL 8.0+),但GORM不原生支持,得Raw执行 - 避免
SELECT *:分页时只查ID、name等必要字段,TEXT/BLOB类大字段单独异步加载
游标分页的稳定性依赖排序字段的索引完整性,而COUNT的准确性取决于条件封装方式——这两点最容易被跳过测试,却在上线后第一个高并发峰值时暴露。



















