2013-04-24 2 views
21

Использование пакетов базы данных/sql и драйверов и Tx не представляется возможным, чтобы определить, была ли транзакция выполнена или откат, не пытаясь повторить попытку и получив ошибку как результат, а затем изучить ошибку, чтобы определить тип ошибки. Я хотел бы иметь возможность определить из объекта Tx, независимо от того, было оно совершено или нет. Конечно, я могу определить и установить другую переменную в функции, которая использует Tx, но у меня их довольно много, и каждый раз это время 2 (переменная и назначение). У меня также есть отложенная функция для выполнения отката, если это необходимо, и ему необходимо передать переменную bool.

Было бы приемлемым, чтобы установить переменную Tx в nil после Commit или Rollback, и GC восстановит любую память, или это нет-нет, или есть лучшая альтернатива?database/sql Tx - обнаружение фиксации или отката

+1

Не уверен, если я понимаю проблему. Вы должны завершить транзакцию с помощью Commit или Rollback, чтобы вы знали, что вы сделали, но вы не хотите помнить об этом в дополнительной переменной? Вы можете обернуть Tx и bool в свой собственный RememberingTx, это уменьшит количество строк. Что касается вопроса GC: не имеет значения, если вы установите нуль или нет: память будет восстановлена, как только ссылка не будет оставлена. Итак: да, вы можете иметь 'var tx * Tx; чик; если cond {tx.Commit; tx = nil} else {tx.Rollback}; чик; если tx == nil {был зачислен} else {был откат} ', но он кажется уродливым. – Volker

+0

Вот что это значит, но есть отложенная функция, которая выполняет откат, если Tx не равен нулю. Как только транзакция будет совершена, Tx не может быть использован в любом случае, поэтому я планирую установить его на нуль. Это не очень, но попытка откат и тестирование сообщения об ошибке тоже не очень. Проблема в том, что AFAIK не может проверить, выполнена ли транзакция из Tx. Я не уверен, почему это было сделано так, возможно, в производительности. –

ответ

73

Зачем вам это нужно? Функция, вызывающая Begin(), также должна звонить Commit() или Rollback() и возвращать соответствующую ошибку.

Например, этот код фиксации или отката в зависимости от того, возвращается ошибка:

func (s Service) DoSomething() (err error) { 
    tx, err := s.db.Begin() 
    if err != nil { 
     return 
    } 
    defer func() { 
     if err != nil { 
      tx.Rollback() 
      return 
     } 
     err = tx.Commit() 
    }() 
    if _, err = tx.Exec(...); err != nil { 
     return 
    } 
    if _, err = tx.Exec(...); err != nil { 
     return 
    } 
    // ... 
    return 
} 

Обратите внимание, как я проверяю error видеть, должен ли я совершить или откат. Однако вышеприведенный пример не обрабатывает паники.

Мне не нравится делать логику commit/rollback для каждой процедуры базы данных, поэтому я обычно обертываю их обработчиком транзакций. Что-то вдоль линий этого:

func Transact(db *sql.DB, txFunc func(*sql.Tx) error) (err error) { 
    tx, err := db.Begin() 
    if err != nil { 
     return 
    } 
    defer func() { 
     if p := recover(); p != nil { 
      tx.Rollback() 
      panic(p) // re-throw panic after Rollback 
     } else if err != nil { 
      tx.Rollback() 
     } else { 
      err = tx.Commit() 
     } 
    }() 
    err = txFunc(tx) 
    return err 
} 

Это позволяет мне сделать это вместо:

func (s Service) DoSomething() error { 
    return Transact(s.db, func (tx *sql.Tx) error { 
     if _, err := tx.Exec(...); err != nil { 
      return err 
     } 
     if _, err := tx.Exec(...); err != nil { 
      return err 
     } 
    }) 
} 

Обратите внимание, что если что-то в моей транзакции паникует она автоматически обрабатывается обработчиком транзакций.

В моей реальной реализации я передаю интерфейс вместо * sql.Tx, чтобы предотвратить нежелательные вызовы Commit() или Rollback().

Вот простой фрагмент кода, чтобы продемонстрировать, как defer работы (печать 4, а не 5):

package main 

func test() (i int) { 
    defer func() { 
     i = 4 
    }() 
    return 5 
} 

func main() { 
    println(test()) 
} 

http://play.golang.org/p/0OinYDWFlx

+0

хороший ответ! Я думаю, вы пропустили «обратный нуль» в конце вашей второй реализации doSomething(). – splinter123

+0

Люк, как и когда ошибка оценивается? Согласно документации, «err» должен получить свою ценность, когда она впервые объявлена ​​в вызове отсрочки.Поэтому для меня это немного сбивает с толку, так как значение «err», используемое при отсрочке, похоже, меняется. – mirage

+0

err объявляется перед отсрочкой через: = (двоеточие равно). Anon func захватывает его. Отсрочка вызывается непосредственно перед возвратом значения. Это позволяет установить его. Когда происходит паника, он восстанавливается, превращается в ошибку, а затем возвращается. Если ошибка происходит каким-либо образом, происходит откат. Наконец, фиксация происходит, если ошибок нет, а err (в настоящее время nil) установлено значение Commits return в случае его ошибок. – Luke

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