分库分表不能由ORM自动实现,必须由业务层控制连接选择与SQL路由;需为每个物理库预建独立sql.DB或gorm.DB实例,分片键应选不变字段(如user_id),用取模或一致性哈希计算目标库表,事务限单库,跨库操作需补偿机制。

分库分表在 Go 框架里不是 ORM 能自动解决的
Go 生态中主流 ORM(如 gorm、sqlx)本身不提供分库分表能力,它只负责单库 SQL 执行。分库分表是业务层决策,必须由你控制连接选择、SQL 路由、主键生成、跨库 Join 等逻辑。依赖“自动分片”的第三方库(如 vitess 或 shardingsphere-proxy)会引入额外运维复杂度,而纯 Go 实现更可控——尤其适合中小规模、分片规则稳定(如按用户 ID 取模)的场景。
用 gorm + 自定义 DB 路由实现分库
核心思路是:不直接用全局 *gorm.DB,而是封装一个 ShardedDB 类型,根据分片键(如 user_id)计算目标库索引,再从连接池中取对应 *gorm.DB 实例。注意以下几点:
-
gorm.Open需为每个物理库单独调用,连接字符串要区分(如db0、db1) - 分库函数必须幂等且无状态,推荐用
user_id % 4这类简单哈希,避免用时间或 UUID 做分片键 - 事务只能在单库内保证一致性;跨库写入需用 TCC 或本地消息表补偿
- 示例路由逻辑:
func (s *ShardedDB) GetDB(userID uint64) *gorm.DB { idx := userID % uint64(len(s.dbs)) return s.dbs[idx] }
分表逻辑必须和业务代码强绑定
分表不像分库能靠中间件拦截,它直接影响 SQL 的 FROM 表名。常见做法是在 DAO 层构造动态表名,例如用户订单按月分表:orders_202405、orders_202406。关键约束有:
- 分表字段(如
created_at)必须出现在所有查询的WHERE条件中,否则无法确定查哪张表 - 禁止
SELECT * FROM orders这类无分表键的全表扫描 - GORM 的
Table()方法可动态指定表名:db.Table("orders_" + formatMonth(order.CreatedAt)).Create(&order) - 如果用 GORM v2,注意
Session不会继承Table设置,每次操作都得显式调用
全局唯一 ID 不能依赖单库自增主键
分库后,各库的 AUTO_INCREMENT 会冲突。必须替换为分布式 ID 生成器。推荐方案:
立即学习“go语言免费学习笔记(深入)”;
- 使用
sony/flake(Snowflake 变种),Go 原生支持,毫秒级时间戳 + 机器 ID + 序列号,生成int64型 ID - 避免用数据库 UUID:存储体积大、索引性能差、非单调导致 B+ 树频繁分裂
- 若已用自增 ID,可改造为「分库偏移」:每库起始值错开(如库 0 从 1 开始、库 1 从 10000001 开始),但扩展性差,不推荐新项目采用
- 注意时钟回拨问题:
sony/flake默认 panic,需捕获并降级为随机 ID 或等待校准
分库分表最易被忽略的点是「聚合查询」——比如统计全站订单数,必须手动合并多个库/表的 COUNT(*) 结果,没法用一条 SQL 解决。这类需求越早识别,越要设计好物化视图或定时汇总任务。


















