#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
}