GORM的FindInBatches本身就是基于主键游标的分批查询实现,非“结合”游标;若显式指定非主键排序(如ORDER BY created_at)或主键非id,会退化为LIMIT OFFSET并卡顿。

直接说结论:GORM 的 FindInBatches 不是“结合”游标机制,它本身就是基于主键游标的分批查询实现;用错场景或忽略排序约束,反而会退化成 LIMIT OFFSET 并卡顿。
为什么 FindInBatches 默认就走游标,但你可能没触发它
很多人调用 FindInBatches 却发现执行计划里还是出现 OFFSET,根本原因是——FindInBatches 要求查询必须有确定的、可延续的顺序。GORM 会自动补 ORDER BY id,但仅限于你没写任何 ORDER BY 且主键是 id 的情况。一旦你显式写了 ORDER BY created_at,GORM 就无法安全推导游标起点,会 fallback 到传统分页逻辑。
- 显式指定
ORDER BY时,必须确保该字段有唯一性或配合主键去重(例如ORDER BY created_at, id) - 如果表主键不是
id(比如叫user_id),GORM 不会自动加ORDER BY user_id,需手动补全 - 使用
Joins或Preload后,GORM 可能放弃游标优化,转而用临时表 + OFFSET 模拟分批
FindInBatches 的参数陷阱:batchSize 不是越大越好
传入 10000 看似一次捞得多,但实际容易触发 MySQL 的 max_allowed_packet 限制,或让单次事务持有锁时间过长。更关键的是:如果某一批查出的数据中,最大主键值重复(比如用非唯一字段排序),下一批可能漏数据或重复。
- 推荐
batchSize控制在100–500,兼顾网络吞吐与事务粒度 - 回调函数内避免做耗时操作(如 HTTP 请求、复杂计算),否则阻塞批次推进
- 不要在回调里修改正在遍历的记录主键值,会导致游标断裂
如何验证是否真正在用游标查询
最可靠的方式不是看文档,而是看生成的 SQL 和执行计划。开启 GORM 日志后,正常游标查询应形如:
SELECT * FROM `users` WHERE `id` > 50000 ORDER BY `id` LIMIT 100
而不是:
SELECT * FROM `users` ORDER BY `id` LIMIT 100 OFFSET 50000
- 用
EXPLAIN查看rows字段:游标查询的rows应接近batchSize;OFFSET 查询则随批次增长线性上升 - 若日志里出现
OFFSET,立刻检查是否误用了Order、Limit或Offset链式调用 - GORM v1.25+ 支持
Session级配置强制禁用自动排序补全,可用于调试游标失效原因
真正难处理的不是怎么写对 FindInBatches,而是当业务要求「跳到第 N 批」或「按非主键字段倒序分页」时,游标天然不支持随机跳转——这时候得自己维护一个带版本号的游标快照表,或者接受用 WHERE created_at < ? 配合复合索引硬扛。别指望框架替你绕过数据库的物理限制。


















