Подтвердить что ты не робот

База данных /sql Tx - обнаружение фиксации или откат

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

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

4b9b3361

Ответ 1

Зачем вам это нужно? Функция, вызывающая 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