多个*gorm.DB实例必须独立初始化并命名区分(如userDB、logDB),不可复用同一变量;DSN需完整且匹配驱动,驱动须下划线导入,PostgreSQL用户名含特殊字符需url.QueryEscape编码,事务无法跨库,须靠业务层补偿实现最终一致性。

多个 *sql.DB 实例必须独立初始化,不能共享 DSN 或变量名
常见错误是用一个 var db *sql.DB 全局变量反复赋值,比如先 db = sql.Open("mysql", dsn1),再 db = sql.Open("postgres", dsn2)。这会导致前一个实例的连接池被丢弃但未关闭,资源泄漏,且后续所有操作都只走最后一个库。
- 每个数据库对应一个明确命名的变量:
userDB、logDB、cacheDB,不可复用同一变量名 - DSN 必须完整包含 host、port、database、query 参数(如
?parseTime=true&loc=Local),主从库哪怕库名相同,host 也必须指向各自地址 - 初始化后立刻调用
db.Ping(),否则连接失败会延迟到首次查询才暴露,线上服务启动即不可用
gorm.Open 多实例共存的前提:驱动注册 + DSN 格式严格匹配
很多人以为 gorm.Open 返回的是“可插拔”的抽象,其实它极度依赖底层驱动行为。漏掉下划线导入或传错 DSN,GORM 不报错,只在第一次 Create 或 Query 时卡死或返回 "invalid connection"。
- MySQL 场景必须有:
import _ "gorm.io/driver/mysql";PostgreSQL 对应import _ "gorm.io/driver/postgres" - PostgreSQL 的用户名含
@或/时,必须用url.QueryEscape编码,否则解析失败却报"pq: password authentication failed" - SQLite 的 DSN 是文件路径,不能带
tcp://;MySQL 的 DSN 不能漏charset=utf8mb4,否则某些中文字段读写异常
负载分发不是靠中间件路由,而是靠连接池参数与实例隔离
没有“自动负载均衡”的魔法开关。所谓多数据库负载管理,本质是控制每个实例的连接池水位,并按业务语义把请求固定打到对应实例上——不是随机选,而是明确归属。
- 主库写压力大?设
userDB.SetMaxOpenConns(50);从库只读?reportDB.SetMaxOpenConns(15),避免主库连接耗尽拖垮报表查询 - 金仓(
gokb)驱动需额外注意:它不兼容标准mysql或postgresDSN 格式,必须用其文档指定的格式,例如user:pass@host:port/database?sslmode=disable - 别在 handler 里动态决定用哪个 DB:先查配置再选实例,容易导致连接池混乱;应在初始化阶段就绑定好
userRepo{db: userDB}这类结构体
事务跨实例绝对失效,补偿逻辑得提前设计
userDB.Transaction() 里调 logDB.Create() 看似能“一起提交”,实际 logDB 操作完全游离于事务之外,回滚也无效。这不是 bug,是数据库协议层限制。
立即学习“go语言免费学习笔记(深入)”;
- 跨库写入必须接受最终一致性:先写主库成功,再异步发消息触发日志写入;失败则走告警+人工核对路径
- 想复用单次连接上下文?用
userDB.Session(&gorm.Session{NewDB: true})可隔离事务内连接,但仅限本实例,不影响其他*gorm.DB - ctx 超时只约束当前 DB:若
ctx, _ := context.WithTimeout(ctx, 3*time.Second)传给userDB.WithContext(ctx).Transaction(...),logDB 的操作仍按自身超时走


















