多数据源 × 事务交互
目标
本页说明在 Juice 中,数据源切换(``engine.With(…)``) 与 事务(``Transaction/Tx``) 如何组合,避免跨库事务误用。
核心结论
engine.With("name")会返回绑定到目标数据源的新Engine视图。juice.Transaction的事务只覆盖**当前 engine 对应的数据源连接**。NestedTransaction只处理“是否已在事务中”,不负责跨数据源一致性。Juice 不提供分布式事务(2PC/XA)抽象;跨库一致性建议由业务侧策略保证。
交互矩阵
场景 |
是否单事务覆盖 |
风险 |
建议 |
|---|---|---|---|
单一数据源内 |
是 |
风险低 |
直接使用 |
事务外先 |
是(仅该 slave) |
写入/只读职责混淆 |
明确读写策略后使用 |
事务回调内临时切换到另一数据源执行 |
否(通常变成两个独立事务域) |
数据不一致、部分提交 |
避免;改为 saga/outbox |
同请求内多次切换数据源并写入 |
否 |
无法原子提交 |
拆分步骤并设计补偿 |
推荐模式
模式一:先选数据源,再开事务
base := juice.ContextWithManager(context.Background(), engine)
writeEngine, err := engine.With("master")
if err != nil {
return err
}
ctx := juice.ContextWithManager(base, writeEngine)
return juice.Transaction(ctx, func(ctx context.Context) error {
_, err := repo.CreateOrder(ctx, req)
return err
})
模式二:读写分离下,写路径固定主库事务
查询可按策略走从库(无事务或只读事务)。
下单、扣减库存、记账等写路径统一走主库事务。
模式三:跨数据源一致性用业务模式兜底
可选策略:
outbox + 异步投递
saga(补偿事务)
幂等键 + 重试
反例(不推荐)
// 外层在 master 开事务
_ = juice.Transaction(ctx, func(ctx context.Context) error {
if _, err := repoA.WriteOnMaster(ctx, a); err != nil {
return err
}
// 在同一回调里切到 slave/other 再写
other, _ := engine.With("other")
otherCtx := juice.ContextWithManager(ctx, other)
_, err := repoB.WriteOnOther(otherCtx, b)
return err
})
上面代码通常不能保证两个写操作原子一致。
排障建议
若出现“部分成功部分失败”,先检查是否在一个业务动作里写了多个数据源。
若事务似乎“没生效”,检查调用链是否混入了另一个
engine.With(...)的上下文。在日志中打印 EnvID(若你在中间件里可拿到)有助于快速定位数据源漂移问题。