beego多数据源需显式注册与调用,注册须在init()中完成且alias唯一,dsn需可连通;每次请求应新建orm实例,事务必须在同一实例内执行。

Beego 的多数据源不是“配好就能自动切”的功能,必须显式注册、显式调用、严格隔离——否则查错库、事务失效、连接池混乱都是常态。
注册多个数据库必须在 init() 或启动早期完成
Beego ORM 的 orm.RegisterDataBase 是全局静态注册,运行时无法新增或修改。一旦启动,所有 alias 就固定了。
- 每个数据源需唯一 alias(如
"mysql"、"pg"、"report"),不能重复 - DSN 必须完整且可连通,否则
RunSyncdb或首次查询会 panic - 别把密码写死在代码里:从配置读取,比如
beego.AppConfig.String("db.mysql.dsn") - 注册顺序无关,但建议按主次排列,方便排查
示例:
func init() {
// 主库
orm.RegisterDataBase("mysql", "mysql", beego.AppConfig.String("db.mysql.dsn"))
// 报表库
orm.RegisterDataBase("pg", "postgres", beego.AppConfig.String("db.pg.dsn"))
// 可选:只读从库
orm.RegisterDataBase("slave", "mysql", beego.AppConfig.String("db.slave.dsn"))
}
每次请求都该用 new orm 实例,别复用全局变量
复用 orm.NewOrm() 返回的实例是并发不安全的,会导致链式查询状态污染、事务错乱、Using() 切库失效。
- HTTP handler 里直接调用
o := orm.NewOrm(),生命周期与请求对齐 - 若只用默认库,无需
o.Using("default");切库才加这句 - 事务必须在同一个
o实例内完成:o.Begin()→o.Commit(),跨实例 = 跨事务上下文 - 性能无负担:
NewOrm()单次耗时
反模式(危险):
var globalOrm orm.Ormer
func init() {
globalOrm = orm.NewOrm() // ❌ 全局单例,goroutine 不安全
}
切换数据库必须显式调用 o.Using("alias")
o.Using() 是实例级状态,只影响后续对该实例的所有操作,不会“记住”上一次用了哪个库。
- 没调用
o.Using()时,默认走"default"—— 这个 alias 必须已注册,否则 panic - 不能靠“先查 mysql,再查 pg”就自动切换:两次查询必须分别
o1.Using("mysql")和o2.Using("pg") - 事务内严禁切换:一个
o.Begin()后,o.Using("pg")会破坏事务一致性,报sql: transaction has already been committed or rolled back - Raw SQL 查询也受
Using()控制:o.Raw("SELECT ...").QueryRow()走的是当前 alias 对应的库
跨库操作只能靠业务层协调,ORM 不支持分布式事务
Beego ORM 底层仍是单个 *sql.Tx,o.Using("mysql").Begin() 和 o.Using("pg").Begin() 是两个完全独立的事务,无法原子提交。
- 不要试图在一个 handler 里对两个库做“要么全成功、要么全回滚”的操作
- 可行方案:用最终一致性(如发消息+补偿)、或把强一致逻辑下沉到单库(如用 MySQL 分区表或 FDW 扩展)
- 如果真要跨库查,用
o.Raw()分别执行再合并结果,但注意字段类型、NULL 处理、分页逻辑得自己对齐 - 别把不同库的
*sql.DB存进同一个 map 然后共用SetMaxOpenConns参数——每个实例必须单独配
最容易被忽略的一点:连接池参数(SetMaxOpenConns、SetConnMaxLifetime)是 per-*sql.DB 的,不是 per-alias。alias 只是路由标识,底层连接池完全独立。











