seata-golang不支持数据库原生xa 2pc,仅实现at模式下的补偿式两阶段流程:业务sql执行→记录undo_log→tc决议commit/rollback,依赖本地事务+undo_log回滚兜底,非数据库层prepare/commit协议。

直接说结论:Seata-golang 不支持标准的、数据库原生的两阶段提交(2PC),它用的是「AT 模式下的类 2PC 流程」,本质是基于 undo_log 的补偿式提交,不是数据库层真正的 prepare/commit 协议。想用 XA 那套语义?目前 seata-golang 官方尚未实现 XA 模式。
AT 模式下怎么走“两阶段”流程
所谓“多阶段提交”,在 Seata-golang AT 模式里实际分三步走:业务 SQL 执行 → 记录 undo_log → TC 统一决议 commit 或 rollback。关键不是数据库锁住资源等第二阶段,而是靠本地事务 + 日志回滚兜底。
-
Begin()后立即生成全局XID,并透传到下游服务(HTTP header、context 传递等) - 每个 RM 在执行业务 SQL 前,自动解析 SQL、生成前镜像(before image)和后镜像(after image),写入本地
undo_log表(需提前建表) - 第一阶段(try):各分支事务本地提交,释放数据库行锁;TC 记录分支状态为
PhaseOne_Done - 第二阶段(finish):TC 根据所有分支上报结果,发
CommitRequest或RollbackRequest;RM 收到 commit 则清空 undo_log,收到 rollback 则按 undo_log 回滚
为什么不能直接用 database/sql 的 tx.Commit()?
因为 Seata-golang 要接管事务生命周期,你手写的 tx.Commit() 会绕过 RM 的分支注册和日志记录,导致 TC 完全不知道这个操作,无法协调全局一致性。
- 必须用
datasource.NewDataSourceProxy()包装原始sql.DB,否则 SQL 不会被拦截 - 所有 DB 操作必须走该代理对象的
Exec()/Query(),不能混用原生db.Exec() - 事务开启不能靠
db.Begin(),而要由globaltransaction.Begin()触发,它会绑定 XID 并注入上下文
seata-golang 的数据源代理容易踩的坑
代理不是透明的——它依赖 driver 层 Hook,对不同数据库驱动兼容性差异大,MySQL 基本可用,PostgreSQL 和 SQLite 支持弱或未验证。
- MySQL 驱动必须用
github.com/go-sql-driver/mysql,且连接串中不能带parseTime=true(会触发 time 类型解析异常) -
undo_log表必须与业务表在同一个数据库实例、同一 schema 下,跨库不支持 - 代理初始化必须在
client.InitPath()之后、任何 DB 操作之前完成,顺序错会导致上下文丢失 - 如果服务启用了连接池复用(如
SetMaxOpenConns),需确保每个连接都携带 XID 上下文,否则分支注册失败
TC 地址配错或不可达时的表现
不是报错退出,而是静默降级为本地事务——这是最危险的情况:你以为在跑分布式事务,其实各服务只在自己 DB 提交,毫无一致性保障。
- 检查
seatago.yml中registry.nacos.server-addr或tc.address是否可连通(telnet host port) - 启动时看日志是否有
register to tc success,没这句基本等于没注册上 - TC 日志里查不到你的 service-group 名称,说明 RM 注册失败,此时所有
globaltransaction调用都退化为 noop
真正麻烦的从来不是配置几行代码,而是当 XID 传丢了、undo_log 写失败、TC 网络抖动时,系统既不 panic 也不报错,只是悄悄地“看起来成功了”。上线前务必用断网、kill TC、删 undo_log 表等方式做破坏性验证。
golang免费学习笔记(深入):立即使用
在学习笔记中,你将探索golang的核心概念和高级技巧!











