Go 无原生分库分表支持,需依赖中间件或手动路由;推荐在 DAO 层实现分片逻辑,注意表名动态计算、连接预初始化、规避跨库事务,并用 Snowflake 等方案生成全局 ID。
分库分表不是 Go 语言原生能力,得靠中间件或手动路由
Go 本身没有内置的分库分表支持,database/sql 只面向单个 *sql.DB 实例。所有分片逻辑必须由业务代码或第三方库承担。常见错误是试图用一个 sql.DB 连接多个物理库——这行不通,连接池无法跨实例协调事务和连接生命周期。
实际落地时有两种主流路径:
- 用代理层(如
ShardingSphere-Proxy或MyCat),Go 应用无感,但失去对分片键、绑定表、分布式事务的细粒度控制 - 在 Go 服务内做分片路由,推荐使用
shard(轻量)、vitess的 Go client(重但成熟),或自研简单路由层(适合规则固定、分片数少的场景)
手动分片路由:按分片键选择 *sql.DB 实例
核心是把分库分表逻辑下沉到 DAO 层,避免污染业务逻辑。典型做法是维护一个 map[string]*sql.DB(key 是库名/表后缀),再写一个路由函数:
func getDB(shardKey string) *sql.DB {
shardID := hashMod(shardKey, 4) // 假设 4 个库
return dbMap[fmt.Sprintf("user_db_%d", shardID)]
}
func getUserByID(ctx context.Context, userID int64) (User, error) {
db := getDB(strconv.FormatInt(userID, 10))
var u User
err := db.QueryRowContext(ctx, "SELECT FROM user_0001 WHERE id = ?", userID).Scan(&u.ID, &u.Name)
return &u, err
}
注意点:
- 表名不能硬编码,需根据
userID % 100算出具体表后缀(如user_0023),否则跨表查询会失败 -
getDB返回的*sql.DB必须提前初始化并完成健康检查,不能每次调用都新建连接 - 不支持跨分片
JOIN或全局ORDER BY,这类需求得靠应用层归并或引入 ES
事务一致性只能靠应用层补偿或两阶段提交(2PC)
Go 的 sql.Tx 天然绑定单个 *sql.DB,跨库事务无法用原生事务保证 ACID。常见错误是以为开启一个 tx 就能操作多个分库——实际只会作用于第一个 db.Begin() 的实例。