因为sql.DB实例的driver和DSN在初始化时已固化,复用会导致连接泄漏、事务失效或panic;必须为每个数据源独立创建sql.DB实例,通过sync.Map缓存并按需注入context,禁止全局变量切换。

为什么 sql.Open 不能直接用于运行时切换数据源
因为 sql.DB 实例内部维护连接池、健康检查和重试逻辑,一旦用 sql.Open 创建,其底层 driver 和连接字符串就已固化。强行替换连接字符串或复用旧 sql.DB 会导致连接泄漏、事务失效或 panic —— 比如调用 db.Close() 后再 db.Query() 会报 "sql: database is closed"。
真实场景中,你通常需要按租户 ID、请求 Header 或配置变更,在不重启服务的前提下切换数据库实例(如 MySQL 主从、分库、测试/生产隔离)。核心思路是:**按需构造新 sql.DB,缓存并复用,而非复用单个全局 db 变量**。
- 每个数据源对应独立的
sql.DB实例,生命周期由你自己管理 - 避免在 handler 中反复调用
sql.Open—— 它不创建连接,但会初始化驱动和校验 DSN,高频调用有开销 - 连接池参数(
SetMaxOpenConns、SetMaxIdleConns)需按数据源特性单独设置,例如读库可放宽 idle 连接数,写库则限制更严
如何用 sync.Map 安全缓存多数据源 sql.DB
不用 map[string]*sql.DB + 全局 mutex,因为高并发下读写竞争严重;sync.Map 对读多写少场景更友好,且避免锁粒度粗导致的性能瓶颈。
注意:键名必须能唯一标识数据源(比如 "tenant_abc_mysql"),不能只用 IP 或端口 —— 同一地址可能对应不同用户/数据库名/SSL 配置。
立即学习“go语言免费学习笔记(深入)”;
- 初始化时先预热常用数据源,避免首次请求时阻塞建连
- 缓存值应为
*sql.DB,不是driver.Conn或 raw connection —— 后者无法复用连接池 - 调用
db.Close()前务必确认无进行中的查询或事务,否则会中断正在执行的语句 - 示例键生成逻辑:
fmt.Sprintf("mysql://%s@%s:%d/%s", user, host, port, dbname),注意对敏感字段脱敏或哈希,避免日志泄露
如何在 Gin/echo 中按请求动态选 sql.DB
中间件里解析租户信息后,把对应 *sql.DB 注入到 context.Context,而不是塞进全局变量或结构体字段 —— 否则跨 goroutine 传递风险高,且难以做单元测试。
典型错误:在中间件里调用 c.Set("db", db),然后 handler 里用 c.MustGet("db") 强转 —— 这种方式类型不安全,且无法静态检查。
- 定义自定义 context key:
var CtxDBKey = struct{}{},避免字符串 key 冲突 - 注入:
c.Request = c.Request.WithContext(context.WithValue(c.Request.Context(), CtxDBKey, db)) - 取出:
db, ok := c.Request.Context().Value(CtxDBKey).(*sql.DB),必须判空 - 不要在 middleware 外部提前 close
sql.DB—— 它应由应用生命周期管理(如服务 shutdown 时统一 close)
切换时如何避免事务跨数据源出错
Go 的 sql.Tx 绑定到特定 *sql.DB 实例,跨数据源复用 tx 会 panic 报 "sql: transaction has already been committed or rolled back" 或静默失败。
常见陷阱:把 “获取 DB” 和 “开启事务” 拆成两步,中间插入了其他逻辑(比如鉴权、日志),结果后续用了错误的 db 开启事务。
- 事务必须在确定目标
*sql.DB后立即调用db.Begin(),不能延迟 - 如果业务需跨库事务(如分布式事务),
sql.Tx无解,得上XA或业务层补偿,Go 标准库不支持 - 测试时用
sqlmock要为每个数据源注册独立 driver name(如"mysql_tenant_a"),否则 mock 会冲突 - 连接超时、重试策略也得按数据源单独配置 —— 某些只读库网络延迟高,不能和主库共用同一套
context.WithTimeout
最麻烦的其实是清理:每个 *sql.DB 的 Close() 必须显式调用,且不能漏 —— 一旦漏掉,连接永远留在池里,直到进程退出。别指望 GC 回收它。


















