2016-10-16 5 views
6

у меня возникают проблемы с поиском некоторых примеров, которые делают три из следующих вещей:Сырье 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 транзакции в голанге? Я не могу найти пример.

EDIT нет примеров here.

ответ

8

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

type Tx struct { }

Tx is an in-progress database transaction.

A transaction must end with a call to Commit or Rollback.

After a call to Commit or Rollback, all operations on the transaction fail with ErrTxDone.

The statements prepared for a transaction by calling the transaction's Prepare or Stmt methods are closed by the call to Commit or 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 { 
      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 { 
      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

Работает идеально. Спасибо чувак. – 1N5818

+1

Действительно полезное сообщение, спасибо. Но я не понимаю, что «транзакция должна завершиться вызовом Commit или Rollback», когда вы возвращаете ошибку, вызванную 'tx.Prepare (некоторые sql ..)', вы не выполняете Commit или Rollback , Почему это? Является ли рассматриваемая сделка должным образом закрытой в этом сценарии? Несмотря на то, что в базовую базу данных не были внесены изменения, не нужно ли нам по-прежнему правильно закрывать транзакцию? – BARJ

+0

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

0

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

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 
} 

 Смежные вопросы

  • Нет связанных вопросов^_^