GORM默认AutoIncrement在分库分表下会冲突,因其依赖各物理表独立自增序列,导致不同分表同时生成相同ID(如sys_user_0和sys_user_1均插入ID=1),引发全局主键重复;必须关闭autoIncrement并改用snowflake等外部ID生成器。

为什么GORM默认AutoIncrement在分库分表下会冲突
GORM 的 AutoIncrement 依赖数据库自增主键,但分库分表后每个物理表的自增序列独立,INSERT INTO sys_user_0 和 INSERT INTO sys_user_1 可能同时生成 ID=1,导致全局主键重复。ShardingSphere 支持 SNOWFLAKE 等分布式 ID 算法,而纯 GORM 方案必须关掉 gorm.Model 的默认主键策略,改用外部生成器。
- 关闭 GORM 自增:
ID uint `gorm:"primaryKey;autoIncrement:false"` - 插入前手动赋值:
user.ID = snowflake.NextID() - 确保所有写入路径(包括关联创建、批量插入)都走同一 ID 生成逻辑
分页查询时ID不连续,COUNT和OFFSET失效怎么办
跨分片分页不能直接用 db.Offset(20).Limit(10),因为各分片数据分布不均,全局第21条可能落在分片0的第5条、分片1的第18条——OFFSET 是按单库算的,不是全局序号。
- 推荐游标分页:用上一页最后一条记录的
id(或id + created_at组合)做条件,例如WHERE id > ? ORDER BY id LIMIT 10 - 若必须偏移分页,需两阶段:先从所有分片查出
id列表(带LIMIT 100防爆),内存合并排序后取目标区间,再用这些id回查完整数据 -
COUNT(*)必须遍历分片累加,不能用db.Count()—— 它只返回当前分片数量
如何让GORM分页插件(如gorm-paginator)兼容分库分表
像 github.com/biezhi/gorm-paginator 这类插件默认只作用于单个 *gorm.DB 实例,直接传入分片后的 db 会漏掉其他分片数据,结果不全。
- 不要把分页插件套在分片
db上;它只能用于单库场景 - 若坚持用,需自行封装:先调用
GetAllShardDBs()获取全部分片实例,对每个调用Paginate()得到子结果,再合并去重、排序、截断 - 更稳妥的做法是绕过插件,用原生
db.Raw()或db.Table()显式指定物理表名(如sys_user_0),配合游标逻辑自己实现
分布式ID生成器与分页游标的协同要点
游标分页依赖单调递增且全局有序的字段,snowflake 生成的 int64 ID 天然满足,但要注意时钟回拨和节点位冲突导致的局部乱序。
- 避免仅用
ID做游标:高并发下同毫秒内多个 ID 可能无序,建议组合id, created_at或id, updated_at - 分页 WHERE 条件必须覆盖所有分片路由键:比如按
user_id % 4分片,则游标查询中WHERE user_id = ? AND id > ?才能精准落到单一分片 - 如果用 ShardingSphere-Proxy,游标字段(如
id)必须是分片键的一部分或已配置为broadcast表,否则 Proxy 无法路由


















