事务无法跨 gorm 数据源生效:db1.transaction() 中调用 db2.create() 时,后者完全游离于事务之外,因每个 gorm.db 封装独立 sql.db,事务仅绑定单个实例。

事务根本不能跨 GORM 数据源生效
直接说结论:db1.Transaction() 里调用 db2.Create(),后者完全游离在事务外——GORM 的事务只绑定单个 *sql.DB 实例,而每个 *gorm.DB 都封装了独立的 *sql.DB。这不是配置问题,是设计限制。你看到的“成功提交”,只是 db1 提交了,db2 的操作早已在默认自动提交模式下落库。
为什么改 DSN 或复用变量切库会丢事务
常见错误包括:
- 用同一个
*gorm.DB变量反复赋值:db = gorm.Open(dsn1)→db = gorm.Open(dsn2),前一个连接池未关闭,连接泄漏;事务上下文被覆盖,db.Transaction()实际作用于最新打开的库,但旧库操作已不可回滚 - 试图通过
USE database_name或SET search_path切换 schema:连接池复用导致后续请求串库,事务隔离彻底失效 - 在中间件里塞进
context.WithValue(ctx, dbKey, db1),但 handler 忘了取值,继续用错全局变量db
动态路由场景下事务该怎么写
租户或分片这类动态场景,必须按请求绑定唯一 *gorm.DB 实例,且事务只能作用于该实例:
- 启动时不做全量初始化,用
sync.Map缓存tenantID → *gorm.DB,首次访问时才调用gorm.Open并设置连接池参数 - 中间件从
r.Header.Get("X-Tenant-ID")解析后,查主库确认租户有效性,再从sync.Map取出对应*gorm.DB,注入context - Handler 中必须显式从
ctx.Value(tenantDBKey)取值,传给 DAO 层;DAO 方法签名应为func(db *gorm.DB, ...),而非依赖全局var db *gorm.DB - 事务块内所有操作(
Create、Update、Delete)必须全部走同一个db实例,不能混用db1和db2
真要跨库一致性,别硬扛 GORM 事务
跨数据源原子性不是 ORM 层能解决的问题:
-
Transaction()函数内部调用另一个库的Create(),不会报错,但也不会回滚——它只是普通非事务语句 - 想强一致?上分布式事务(如 Seata),但成本高、运维重,多数业务不值得
- 更现实的做法是“尽力而为”:先提交主库,再异步发消息触发日志库写入;失败靠补偿任务修复,比如定时扫描主库成功但日志库缺失的记录
- 测试时特别容易漏掉这点:本地单库跑通,上线双库后数据不一致,因为事务没真正兜住
*gorm.DB 实例的生命周期、上下文传递路径、以及 DAO 层是否真正消费了路由后的实例,三者缺一不可。











