Необработанные транзакции sql с подготовленными инструкциями golang

#postgresql #go #transactions #acid

# #postgresql #Вперед #транзакции #acid

Вопрос:

У меня возникли проблемы с поиском некоторых примеров, которые выполняют три из следующих действий:

1) Разрешить необработанные транзакции sql в golang.

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

Что дает? Возможны ли транзакции, совместимые с ACID, в golang? Я не могу найти пример.

РЕДАКТИРОВАТЬ здесь нет примеров.

Ответ №1:

Да, Go имеет отличную реализацию транзакций sql. Мы начинаем транзакцию с db.Begin и можем завершить ее с помощью tx.Commit, если все идет хорошо, или с помощью tx.Rollback в случае ошибки.

введите Tx struct { }

Tx — это незавершенная транзакция базы данных.

Транзакция должна завершаться вызовом для фиксации или отката.

После вызова для фиксации или отката все операции с транзакцией завершаются ошибкой 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()
}
 

У меня есть полный пример здесь

Комментарии:

1. Действительно полезный пост, спасибо. Но чего я не понимаю A transaction must end with a call to Commit or Rollback. , хотя, когда вы возвращаете ошибку, вызванную tx.Prepare(some sql..) тем, что вы не выполняете фиксацию или откат. Почему это так? Правильно ли закрыта рассматриваемая транзакция в этом сценарии? Даже если в базовую базу данных не было внесено никаких изменений, разве нам не нужно правильно закрыть транзакцию?

2. Этот код неверен для Go1.4 или более ранней версии, tx.Commit() вернет связанное с ним соединение обратно в пул, что произойдет до stmt. Close(), это может привести к одновременному доступу к базовому соединению, что приведет к непоследовательности состояния соединения. как указано здесь: go-database-sql.org/prepared.html

3. @YandryPozo Это звучит неправильно для меня. Как указывалось выше, согласно golang.org/pkg/database/sql/#Tx , транзакция должна завершиться откатом или фиксацией. Согласно тому же документу, Tx — это транзакция, поэтому, как только у вас есть Tx, у вас есть транзакция.

4. @MarceloCantos вы правы, если у нас есть созданная транзакция, мы все равно должны ее завершить. Я только что отредактировал свой ответ, спасибо за ваш улов!

5. @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, amp;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
}