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

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

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? Я не могу найти пример.

ИЗМЕНИТЬ без примеров здесь.


person Alexander Kleinhans    schedule 16.10.2016    source источник


Ответы (2)


В Yes Go реализована отличная реализация SQL-транзакций транзакций. Мы начинаем транзакцию с 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()
}

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

person Yandry Pozo    schedule 17.10.2016
comment
Действительно полезный пост, спасибо. Но чего я не понимаю, A transaction must end with a call to Commit or Rollback., когда вы возвращаете ошибку, вызванную tx.Prepare(some sql..), вы не выполняете фиксацию или откат. Это почему? Правильно ли закрыта рассматриваемая транзакция в этом сценарии? Даже если в базовую базу данных не было внесено никаких изменений, разве нам не нужно должным образом закрыть транзакцию? - person BARJ; 26.04.2017
comment
Этот код неверен для Go1.4 или более ранней версии, tx.Commit() освободит связанное с ним соединение обратно в пул, что произойдет до stmt.Close(), это может привести к одновременному доступу к базовому соединению. , что делает состояние подключения несогласованным. как указано здесь: go-database-sql.org/prepared.html - person Khaled Jouda; 29.04.2017
comment
@YandryPozo Мне это кажется неправильным. Как указано выше, согласно golang.org/pkg/database/sql/#Tx транзакция должна заканчиваться откатом или фиксацией. Согласно тому же документу, Tx — это транзакция, поэтому, как только у вас есть Tx, у вас есть транзакция. - person Marcelo Cantos; 23.01.2018
comment
@MarceloCantos, вы правы, если у нас есть созданная транзакция, мы все равно должны ее завершить. Я только что отредактировал свой ответ, спасибо за ваш улов! - person Yandry Pozo; 01.07.2018
comment
@BARJ, ты прав, я отредактировал свой ответ, спасибо за комментарий! - person Yandry Pozo; 01.07.2018
comment
Зачем использовать фигурные скобки для разделения двух действий? - person dev-x; 30.11.2018
comment
что такое (thing_1, что угодно) ?? - person ; 23.02.2020
comment
@OlegzandrDenman параметры, переданные в исходном вопросе. - person Yandry Pozo; 01.03.2020

Я придумал возможное решение для отката при любом сбое без каких-либо существенных недостатков. Я довольно новичок в Голанге, хотя могу ошибаться.

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
}
person BARJ    schedule 26.04.2017