使用golang预处理语句的原始sql事务

时间:2016-10-16 23:24:36

标签: postgresql go transactions acid

我无法找到一些可以执行以下三项操作的示例:

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的交易?我找不到一个例子。

EDIT 没有例子here

2 个答案:

答案 0 :(得分:13)

是Go有一个很好的sql transactions实现。我们使用db.Begin启动交易,如果一切顺利,我们可以使用tx.Commit结束,如果出现错误,我们可以使用tx.Rollback结束。

  

输入Tx struct {}

     

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

答案 1 :(得分:0)

我想出了一个可能的解决方案,可以在没有任何明显缺陷的情况下回滚任何故障。我对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
}