You need to enable JavaScript to run this app.
优惠活动
大模型
产品
解决方案
定价
更多

如何通过GORM DBResolver为多数据源复用单个连接池

问题描述

我使用GORM DBResolver对接多数据源(非副本架构),当前实现代码如下:

var databases = []string{"db_tenant1", "db_tenant2", "db_tenant3"}
connString := "postgres://user:password@localhost:5432/postgres?sslmode=disable"
db, err := gorm.Open(postgres.Open(connString), &gorm.Config{})
require.NoError(t, err)

dr := &dbresolver.DBResolver{}
for _, dbname := range databases {
    connString := fmt.Sprintf("postgres://%s:%s@localhost:5432/%s?sslmode=disable", "user", "password", dbname)
    dr = dr.Register(dbresolver.Config{
        Sources: []gorm.Dialector{postgres.Open(connString)},
        Policy:  dbresolver.RandomPolicy{},
    }, dbname, &TheObject{})
}
err = db.Use(dr.SetMaxOpenConns(1))
require.NoError(t, err)

for idx, dbname := range databases {
    result := db.Clauses(dbresolver.Use(dbname), dbresolver.Write).Create(TheObject{})
    require.NoError(t, result.Error)
}
require.NoError(t, err)

执行SELECT * FROM pg_stat_activity后发现GORM为每个数据库创建了独立连接:

12379   postgres    3616
16496   db_tenant1  3617
16606   db_tenant2  3618
16716   db_tenant3  3619

我希望所有数据库(模式)能复用单个连接池中的连接。

解决方案

你现在的问题出在:每个租户数据库都通过独立的postgres.Open()创建了新的Dialector,每个Dialector对应独立的连接池,自然会生成多个独立连接。要复用连接池,核心就是共用同一个底层连接,通过切换数据库/模式来访问不同租户的数据,具体可以这么做:

方案一:用PostgreSQL的SET search_path切换Schema(如果租户用Schema隔离)

如果db_tenant1这些是PostgreSQL的Schema而非独立数据库,根本不用给每个Schema注册数据源,初始连接到主库就行,操作前切换Schema即可:

var schemas = []string{"db_tenant1", "db_tenant2", "db_tenant3"}
connString := "postgres://user:password@localhost:5432/postgres?sslmode=disable"
db, err := gorm.Open(postgres.Open(connString), &gorm.Config{})
require.NoError(t, err)

// 配置连接池,这里只开1个连接
sqlDB, err := db.DB()
require.NoError(t, err)
sqlDB.SetMaxOpenConns(1)

for _, schema := range schemas {
    // 切换到目标Schema
    if err := db.Exec("SET search_path TO ?", schema).Error; err != nil {
        require.NoError(t, err)
    }
    // 执行创建操作
    result := db.Create(&TheObject{})
    require.NoError(t, result.Error)
}

方案二:复用连接池,动态切换独立数据库

如果确实是完全独立的数据库,可以通过自定义DBResolver的回调,拿到连接后切换数据库,复用同一个连接池:

var databases = []string{"db_tenant1", "db_tenant2", "db_tenant3"}
connString := "postgres://user:password@localhost:5432/postgres?sslmode=disable"
db, err := gorm.Open(postgres.Open(connString), &gorm.Config{})
require.NoError(t, err)

// 配置全局连接池,只开1个连接
sqlDB, err := db.DB()
require.NoError(t, err)
sqlDB.SetMaxOpenConns(1)

// 只注册一个数据源,通过Resolver回调确定要使用的数据库
dr := dbresolver.Register(dbresolver.Config{
    Sources: []gorm.Dialector{postgres.Open(connString)},
    Resolver: dbresolver.ResolverFunc(func(destination interface{}, op dbresolver.Operation) string {
        // 实际业务中可以从上下文、请求参数等地方获取当前租户的数据库名
        // 这里示例用上下文传递,你可以根据自己的逻辑调整
        ctx := context.Background()
        return ctx.Value("tenant_db").(string)
    }),
    ConnPool: sqlDB, // 强制复用同一个连接池
}).SetMaxOpenConns(1)

if err := db.Use(dr); err != nil {
    require.NoError(t, err)
}

// 循环操作每个租户数据库
for _, dbname := range databases {
    ctx := context.WithValue(context.Background(), "tenant_db", dbname)
    // 先切换到目标数据库
    if err := db.WithContext(ctx).Exec("CONNECT TO ?", dbname).Error; err != nil {
        require.NoError(t, err)
    }
    // 执行创建操作
    result := db.WithContext(ctx).Create(&TheObject{})
    require.NoError(t, result.Error)
}

注意点

  • 别再为每个租户创建独立的Dialector了,这是产生多连接的根本原因。
  • PostgreSQL里切换独立数据库用CONNECT TO dbname,切换Schema用SET search_path,选哪种取决于你的租户隔离方式。
  • 连接池参数一定要设置在底层的sql.DB实例上,而不是DBResolver的每个数据源配置里。

内容的提问来源于stack exchange,提问作者mic

相关产品推荐
方舟 Agent Plan

超全模态模型 × Harness 升级,最新支持 Deepseek-V4.1-Flash、GLM-5.3 系列、Doubao-Seedream-5.0-pro、Kimi-K3 (部分), 限时 9.9 元起

最近更新时间:2026.07.30 13:03:31