
Go 标准库 database/sql 完全支持 ACID 事务,通过 db.Begin() 启动事务、tx.Prepare() 创建事务内预处理语句,并在出错时调用 tx.Rollback() 回滚,成功后调用 tx.Commit() 提交。
go 标准库 `database/sql` 完全支持 acid 事务,通过 `db.begin()` 启动事务、`tx.prepare()` 创建事务内预处理语句,并在出错时调用 `tx.rollback()` 回滚,成功后调用 `tx.commit()` 提交。
在 Go 中,不能将多个 SQL 命令(如 BEGIN; INSERT; INSERT; END;)合并进单个预处理语句——这正是你遇到 pq: cannot insert multiple commands into a prepared statement 错误的根本原因。PostgreSQL(及多数主流驱动)明确禁止在 Prepare() 中传入多语句字符串,因为预处理语句本质是单次编译、多次执行的单一操作单元,而事务控制(BEGIN/COMMIT/ROLLBACK)必须由 Go 的 sql.Tx 显式管理,而非嵌入 SQL 字符串。
正确的做法是:
✅ 使用 db.Begin() 获取事务对象 *sql.Tx;
✅ 对每个 DML 语句分别调用 tx.Prepare()(注意:必须使用 tx.Prepare,而非 db.Prepare,以确保语句绑定到当前事务上下文);
✅ 每次 Exec() 后检查错误,失败立即 tx.Rollback() 并返回;
✅ 所有操作成功后,显式调用 tx.Commit()。
以下是结构清晰、生产可用的示例代码:
func doubleInsert(db *sql.DB, thing1, thing2, whatever interface{}) error {
// 1. 开启事务
tx, err := db.Begin()
if err != nil {
return fmt.Errorf("failed to begin transaction: %w", err)
}
defer func() {
// 确保 tx 被正确关闭(若未提交/回滚,panic 会触发 defer)
if tx != nil {
tx.Rollback() // 实际上,Commit/Rollback 后再调用会返回 ErrTxDone,安全
}
}()
// 2. 为 table_1 准备并执行插入
stmt1, err := tx.Prepare(`INSERT INTO table_1 (thing_1, whatever) VALUES ($1, $2)`)
if err != nil {
tx.Rollback()
return fmt.Errorf("failed to prepare table_1 insert: %w", err)
}
defer stmt1.Close()
if _, err := stmt1.Exec(thing1, whatever); err != nil {
tx.Rollback()
return fmt.Errorf("failed to insert into table_1: %w", err)
}
// 3. 为 table_2 准备并执行插入
stmt2, err := tx.Prepare(`INSERT INTO table_2 (thing_2, whatever) VALUES ($1, $2)`)
if err != nil {
tx.Rollback()
return fmt.Errorf("failed to prepare table_2 insert: %w", err)
}
defer stmt2.Close()
if _, err := stmt2.Exec(thing2, whatever); err != nil {
tx.Rollback()
return fmt.Errorf("failed to insert into table_2: %w", err)
}
// 4. 提交事务
if err := tx.Commit(); err != nil {
return fmt.Errorf("failed to commit transaction: %w", err)
}
tx = nil // 标记已提交,避免 defer 中重复 Rollback
return nil
}⚠️ 关键注意事项:
- *不要复用 `sql.Stmt
跨事务**:每个tx.Prepare()返回的语句仅对当前事务有效,Commit()或Rollback()` 后自动失效; -
显式错误处理不可省略:每次
Exec()后必须检查错误并主动Rollback(),否则事务可能挂起或数据不一致; -
defer stmt.Close()是良好实践,但需注意它在Rollback()后仍安全(底层会忽略); - 若需更简洁的错误传播,可结合
github.com/pkg/errors或 Go 1.13+ 的fmt.Errorf(..., "%w")实现错误链; - 对于复杂业务逻辑,建议封装事务模板函数(如
WithTx(func(*sql.Tx) error) error),进一步提升复用性与可读性。
综上,Go 不仅完全支持 ACID 事务,其设计还强制开发者显式管理事务生命周期,从而避免隐式行为带来的不确定性——这是构建健壮数据层的重要保障。

















