唯一稳妥方案是手写Limit+Offset分页,因CockroachDB不支持gorm/pagination等插件(子查询含OFFSET报错)、OFFSET值敏感需强校验、必须显式Order保证稳定性、Count需隔离Session、且OFFSET超5万行性能骤降,应改用游标分页。

直接用 Limit + Offset 是唯一稳妥方案,CockroachDB 不支持 GORM 的第三方分页插件(如 gorm/pagination),且其对 OFFSET 的执行策略和 PostgreSQL 不同——偏移量大时不会“扫描丢弃”,而是会做分布式范围扫描,但依然有性能拐点;必须显式 Order、独立 Count、参数强校验。
为什么不能用 gorm/pagination 或其他封装分页库
CockroachDB 虽然兼容 PostgreSQL 协议,但它的查询计划器对 LIMIT/OFFSET 的处理逻辑更严格:不支持在子查询中嵌套带 OFFSET 的语句(常见于某些分页插件的 COUNT 实现),会导致 ERROR: unsupported subquery with OFFSET。而 gorm/pagination 等库内部默认尝试复用链式 DB 实例做 COUNT,或自动拼装带子查询的统计 SQL,在 CockroachDB 上大概率失败。它也不是 CockroachDB 官方适配的扩展,v2.2+ 的 GORM Scope 行为变更后,这类插件已普遍失效。
Offset 和 Limit 的顺序与计算必须手写
CockroachDB 对 OFFSET 值敏感,传负数或超大整数(如 > 2^63-1)会直接报错 invalid offset value,不像 MySQL 那样静默截断。所以不能依赖任何“自动算 Offset”的封装。
-
page必须 ≥ 1,page = 0或空字符串时强制设为1 -
page_size必须 > 0 且 ≤ 100(建议硬上限),Limit(0)在 CockroachDB 中行为未定义,部分版本返回空结果,部分 panic - Offset 值恒为
(page - 1) * page_size,别信“第 N 页就 Offset(N)”这种错觉 - 必须先调
Offset()再调Limit()—— 虽然 GORM v2 允许互换,但 CockroachDB 驱动在某些版本中对调序敏感,固定顺序可避坑
Order BY 不是可选项,而是稳定性前提
CockroachDB 的分布式事务模型导致无序分页结果极易重复或遗漏:同一查询两次执行,因各节点返回顺序不一致,LIMIT 10 OFFSET 100 可能跳过某条记录。它要求排序字段必须是「确定性、单调、索引覆盖」的组合。
- 首选
Order("id ASC")(主键天然全局有序) - 若用时间字段,必须搭配二级排序,例如
Order("created_at DESC, id DESC")—— 单独created_at DESC在高并发写入下必然出现同值,排序不稳定 - 避免
Order("RANDOM()")或任何非确定性函数,CockroachDB 不支持该用法,会报错 - 确保排序字段已建索引,否则查询会被拒绝或极慢(CockroachDB 默认启用
require_explicit_index模式)
Count 查询必须隔离 Session 且慎用子查询
CockroachDB 不允许在同一个事务中对同一张表做带 LIMIT/OFFSET 的查询后再执行 COUNT(*),否则会复用前序条件并报错 cannot use LIMIT/OFFSET in COUNT context。同时,它对子查询嵌套深度有限制,复杂 JOIN 的 SELECT COUNT(*) FROM (SELECT ...) 容易触发 nesting too deep。
- 简单场景:用
db.Session(&gorm.Session{NewDB: true}).Model(&User{}).Where(...).Count(&total)强制新建会话 - 带
Joins或Preload的场景:改用手写Raw查询,例如db.Raw("SELECT count(*) FROM users u JOIN profiles p ON u.id = p.user_id WHERE p.active = ?", true).Scan(&total) - 不要在事务里连发
Count+Find—— CockroachDB 的快照隔离(SI)虽保证一致性,但两次查询间若发生 Schema 变更(如 ADD COLUMN),可能触发重试或错误
最易被忽略的是:CockroachDB 的分页性能拐点比 PostgreSQL 更早,OFFSET 超过 5 万行后延迟明显上升,不是因为扫描,而是跨节点协调开销变大。此时游标分页(WHERE id > ?)不是“优化建议”,而是生产必需 —— 它绕开了分布式偏移协调,响应稳定在毫秒级。


















