必须为每个数据源独立初始化sql.DB或gorm.DB实例,不能复用同一变量切换库;需分别调用sql.Open或gorm.Open、单独设置连接池参数、提前导入对应驱动,并通过sync.Map+context实现租户/读写路由。

不能靠一个 *sql.DB 或 *gorm.DB 变量“切换”多个库,Go 原生不支持运行时换驱动或换 DSN;必须为每个数据源独立初始化实例,否则事务错乱、连接泄漏、panic 都是确定会发生的事。
多数据源必须各自调用 sql.Open 或 gorm.Open
哪怕只是 MySQL 的两个库(user_db 和 order_db),host/port 完全一样,仅 database 名不同,也得走两套初始化流程:
userDB, err := sql.Open("mysql", "user:pass@tcp(127.0.0.1:3306)/user_db?...")orderDB, err := sql.Open("mysql", "user:pass@tcp(127.0.0.1:3306)/order_db?...")- 别用全局
var db *sql.DB覆盖赋值——前一个库的连接池不会自动关闭,连接持续泄漏 - GORM 同理:
userDB, _ := gorm.Open(mysql.Open(dsn1), ...)和orderDB, _ := gorm.Open(mysql.Open(dsn2), ...)必须是两个独立变量
驱动注册必须在编译期完成,不能运行时动态 import
sql: unknown driver "postgres" 这类错误不是配置写错了,而是你没在 main 包里 blank-import 对应驱动:
- MySQL:加
_ "github.com/go-sql-driver/mysql" - PostgreSQL:加
_ "github.com/lib/pq"或_ "github.com/jackc/pgx/v5/pgxpool"(GORM v2 推荐 pgx) - SQLite:加
_ "github.com/mattn/go-sqlite3" - 所有可能用到的驱动都得提前写进
main.go,哪怕某些环境不用——运行时无法加载新驱动
连接池参数必须按库单独设置,不能共用一套
主库和从库、用户库和日志库的流量特征完全不同,混用 SetMaxOpenConns 会导致雪崩:
立即学习“go语言免费学习笔记(深入)”;
-
userDB.SetMaxOpenConns(50)(高并发写+强一致读) -
reportDB.SetMaxOpenConns(10)(低频大查询,连接生命周期长) -
logDB.SetConnMaxLifetime(30 * time.Minute)(日志库连接可更持久) - 漏设
SetMaxOpenConns默认为 0(无上限),数据库容易被打满
租户/读写分离路由必须靠 sync.Map + context,不能硬编码 if-else
把数据库选择逻辑塞进 handler 或 DAO 层,等于把路由和业务耦死,后续加新租户或切主从时就得改一堆地方:
- 入口中间件从
r.Header.Get("X-Tenant-ID")提取标识,查主库确认有效性后,调用tenantDBs.LoadOrStore(tenantID, newDB(cfg)) - handler 中通过
ctx.Value(tenantDBKey).(*sql.DB)拿实例,不 fallback 到全局变量 - 读写分离靠显式标记:
ctx = context.WithValue(ctx, "rw_hint", "write"),而不是解析 SQL 文本 - 别在 Gin Group 里试图“绑定 DB”——路由和 DB 是正交层,Group 只管 URL 匹配,不管连接归属
最容易被忽略的是驱动导入漏下划线、DSN 特殊字符未编码(比如 PostgreSQL 密码含 @)、事务跨实例调用——这些错误不会在编译时报,而是在第一次查询时静默卡死或报 invalid connection。


















