gorm 无法用于非关系型数据库分页,因其 limit、offset 等方法基于 sql 语义,依赖表结构、确定性排序和可预测 count;而 mongodb 的 skip/limit 效率低,elasticsearch from/size 有 10000 限制,redis 分页无偏移稳定性,强行适配会导致 panic、忽略排序或报错。

GORM 是关系型数据库 ORM,不支持非关系型数据库代理(如 MongoDB、Redis、Elasticsearch)——所谓“GORM 非关系型代理”本身是个伪命题,强行适配只会踩坑。
为什么 GORM 无法用于非关系型数据库分页
GORM 的 Limit、Offset、Order、Count 等方法全部基于 SQL 语义生成 AST 并拼接 SQL 字符串。它依赖:
• 关系模型(表、行、列、JOIN)
• 标准化排序(ORDER BY 字段确定性)
• 可预测的 COUNT(*) 行为
• 主键/索引字段可直接参与 WHERE + LIMIT 推导
而 MongoDB 的 skip()/limit() 语义不同(skip 是内存跳过,大数据量极慢),Elasticsearch 的 from/size 有 10000 深度限制,Redis 列表分页靠 LRANGE 无偏移稳定性——这些都和 GORM 的抽象层不兼容。
常见错误:用 gorm.io/gorm + 第三方 driver 假装支持 NoSQL
有人尝试用 gorm.io/driver/mongodb(非官方,社区维护)或自定义 gorm.Dialector 接入 MongoDB,结果发现:
• db.Offset(1000).Limit(20) 生成的不是 skip(1000).limit(20),而是 panic 或静默忽略
• db.Order("created_at DESC") 被丢弃,因 driver 未实现 Explain 或 BuildOrderClause
• db.Count(&total) 直接报错 unsupported operation: Count on non-SQL backend
• Preload 在 MongoDB 中无嵌套文档 JOIN 概念,调用即 crash
真正可行的替代方案:按存储类型分层处理
不要试图让 GORM “统一所有数据源”。正确做法是:
• 关系型数据(MySQL/PostgreSQL):坚持用 GORM + Limit/Offset 或游标分页,严格校验 page 和 page_size,独立查 Count
• MongoDB:用官方 go.mongodb.org/mongo-driver/mongo,分页走 FindOptions.SetSkip().SetLimit(),但必须配合 Sort + 索引字段(如 {"created_at": -1, "_id": -1}),避免 skip > 10000
• Elasticsearch:用 olivere/elastic/v7,分页优先用 search_after(游标),禁用 from/size 超过 10000
• Redis:列表类用 LRANGE key start stop(start/stop 是下标,不是 offset),集合类用 SSCAN 游标迭代,不适用“页码”概念
如果非要共用接口,封装的是逻辑,不是 GORM 实例
可以定义统一的分页参数结构和返回结构,但底层必须切换实现:
• 不共享 *gorm.DB,也不传 db.Scopes(...) 到 NoSQL 层
• 分页逻辑写在 service 层:判断数据源类型后,调用 mysqlRepo.ListPage() 或 mongoRepo.ListCursor()
• 错误处理要分开:MongoDB 的 ErrNoDocuments 和 MySQL 的 sql.ErrNoRows 不能混用
• 排序字段映射需手动对齐:比如 GORM 里写 Order("created_at DESC, id DESC"),MongoDB 就得转成 bson.D{{"created_at", -1}, {"_id", -1}}
最常被忽略的一点:NoSQL 分页的“总数”往往不可靠或代价极高(比如 MongoDB 的 countDocuments 全集合扫描)。别在接口里硬塞 Total 字段——用 HasNext + 查 size + 1 条更实际。











