带有 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 事务的主要内容,如果未能解决你的问题,请参考以下文章

从 PDO 准备好的语句中获取原始 SQL 查询字符串

从 PDO 准备好的语句中获取原始 SQL 查询字符串

Golang规避 SQL 注入风险

PHP在循环中准备语句和事务[重复]

带有 sql 转义的动态 mysql 查询是不是与准备好的语句一样安全?

带有 SQL Server 和准备好的语句的 PHP PDO