带有 golang 准备语句的原始 sql 事务
Posted
技术标签:
【中文标题】带有 golang 准备语句的原始 sql 事务【英文标题】:Raw sql transactions with golang prepared statements 【发布时间】:2017-02-25 20:37:27 【问题描述】:我很难找到一些执行以下三项操作的示例:
1) 在 golang 中允许原始 sql 事务。
2) 使用准备好的语句。
3) 查询失败时回滚。
我想做这样的事情,但有准备好的陈述。
stmt, stmt_err := db.Prepare(`
BEGIN TRANSACTION;
-- Insert record into first table.
INSERT INTO table_1 (
thing_1,
whatever)
VALUES($1,$2);
-- Inert record into second table.
INSERT INTO table_2 (
thing_2,
whatever)
VALUES($3,$4);
END TRANSACTION;
`)
if stmt_err != nil
return stmt_err
res, res_err := stmt.Exec(
thing_1,
whatever,
thing_2,
whatever)
当我运行它时,我得到这个错误:
pq: cannot insert multiple commands into a prepared statement
什么给了?在 golang 中是否可以进行符合 ACID 的事务?我找不到例子。
编辑 没有例子here。
【问题讨论】:
【参考方案1】:是的,Go 有一个很好的 sql transactions 实现。我们以db.Begin 开始事务,如果一切顺利,我们可以以tx.Commit 结束它,或者以tx.Rollback 结束它以防出错。
类型 Tx 结构
Tx 是一个正在进行的数据库事务。
事务必须以调用 Commit 或 Rollback 结束。
在调用 Commit 或 Rollback 后,事务上的所有操作都会失败并显示 ErrTxDone。
通过调用事务的 Prepare 或 Stmt 方法为事务准备的语句通过调用 Commit 或 Rollback 关闭。
还请注意,我们使用事务变量 tx.Prepare(...) 准备查询
您的函数可能如下所示:
func doubleInsert(db *sql.DB) error
tx, err := db.Begin()
if err != nil
return err
stmt, err := tx.Prepare(`INSERT INTO table_1 (thing_1, whatever)
VALUES($1,$2);`)
if err != nil
tx.Rollback()
return err
defer stmt.Close()
if _, err := stmt.Exec(thing_1, whatever); err != nil
tx.Rollback() // return an error too, we may want to wrap them
return err
stmt, err := tx.Prepare(`INSERT INTO table_2 (thing_2, whatever)
VALUES($1, $2);`)
if err != nil
tx.Rollback()
return err
defer stmt.Close()
if _, err := stmt.Exec(thing_2, whatever); err != nil
tx.Rollback() // return an error too, we may want to wrap them
return err
return tx.Commit()
我有一个完整的例子here
【讨论】:
非常有用的帖子,谢谢。但是我不明白,A transaction must end with a call to Commit or Rollback.
,当您返回由tx.Prepare(some sql..)
引起的错误时,您不会执行提交或回滚。这是为什么?在那种情况下,有问题的交易是否正确关闭?即使底层数据库没有做任何更改,我们是否仍然需要正确关闭事务?
此代码对于 Go1.4 或更早版本不正确,tx.Commit() 会将与其关联的连接释放回池中,这将在 stmt.Close() 之前发生,这可能导致并发访问底层连接,导致连接状态不一致。如此处所述:go-database-sql.org/prepared.html
@YandryPozo 这对我来说听起来不对。如上所述,根据golang.org/pkg/database/sql/#Tx,事务必须以 Rollback 或 Commit 结束。根据同一个文档,Tx 是一个交易,所以只要你有一个 Tx,你就有一个交易。
@MarceloCantos 你是对的,如果我们有一个已创建的交易,我们无论如何都必须完成它。我刚刚编辑了我的答案,感谢您的关注!
@BARJ 你说得对,我编辑了我的答案,谢谢你的评论!【参考方案2】:
我想出了一个可能的解决方案,可以在没有任何重大缺陷的情况下回滚任何失败。不过,我对 Golang 还是很陌生,我可能是错的。
func CloseTransaction(tx *sql.Tx, commit *bool)
if *commit
log.Println("Commit sql transaction")
if err := tx.Commit(); err != nil
log.Panic(err)
else
log.Println("Rollback sql transcation")
if err := tx.Rollback(); err != nil
log.Panic(err)
func MultipleSqlQuriesWithTx(db *sql.DB, .. /* some parameter(s) */) (.. .. /* some named return parameter(s) */, err error)
tx, err := db.Begin()
if err != nil
return
commitTx := false
defer CloseTransaction(tx, &commitTx)
// First sql query
stmt, err := tx.Prepare(..) // some raw sql
if err != nil
return
defer stmt.Close()
res, err := stmt.Exec(..) // some var args
if err != nil
return
// Second sql query
stmt, err := tx.Prepare(..) // some raw sql
if err != nil
return
defer stmt.Close()
res, err := stmt.Exec(..) // some var args
if err != nil
return
/*
more tx sql statements and queries here
*/
// success, commit and return result
commitTx = true
return
【讨论】:
以上是关于带有 golang 准备语句的原始 sql 事务的主要内容,如果未能解决你的问题,请参考以下文章