web-dev-qa-db-ja.com

database / sql Tx-コミットまたはロールバックの検出

データベース/ SQLおよびドライバーパッケージとTxを使用すると、トランザクションがコミットされたかロールバックされたかを、別のトランザクションを試行して結果としてエラーを受信せずに検出し、エラーを調べてタイプを判別することはできないようですエラー。コミットされているかどうかをTxオブジェクトから判別できるようにしたいと思います。確かに、Txを使用する関数で別の変数を定義および設定できますが、それらの数はかなり多く、毎回2倍です(変数と代入)。また、必要に応じてロールバックを実行するための遅延関数があり、bool変数を渡す必要があります。

コミットまたはロールバックの後にTx変数をnilに設定することは許容されますか?GCはメモリを回復しますか、それともノーノーですか、またはより良い代替手段がありますか?

34
Brian Oh

Begin()Commit()、およびRollback()が同じ関数内にあることを確認する必要があります。トランザクションの追跡が容易になり、deferを使用してトランザクションが適切に閉じられていることを確認できます。

これがエラーの返されたかどうかに応じてコミットまたはロールバックを行う例です。

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

これは少し反復的になる可能性があります。これを行う別の方法は、トランザクションハンドラーを使用してトランザクションをラップすることです。

_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() // err is non-nil; don't change it
        } else {
            err = tx.Commit() // err is nil; if Commit returns error update err
        }
    }()
    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
        }
        return nil
    })
}
_

これにより、私のトランザクションが簡潔に保たれ、トランザクションごとに適切に処理されます。

私のトランザクションハンドラーでは、recover()を使用してパニックをキャッチし、ロールバックがすぐに行われるようにします。パニックが予想される場合にコードがキャッチできるように、パニックを再スローします。通常の状況では、パニックは発生しません。代わりにエラーを返す必要があります。

パニックを処理しなかった場合、トランザクションは最終的にロールバックされます。コミットされていないトランザクションは、クライアントが切断したとき、またはトランザクションがガベージコレクションされたときに、データベースによってロールバックされます。ただし、トランザクションが自動的に解決するのを待つと、他の(未定義の)問題が発生する可能性があります。したがって、できるだけ早く解決することをお勧めします。

すぐに明確にならない場合があるのは、戻り変数がキャプチャされた場合、deferはクロージャー内の戻り値を変更できることです。トランザクションハンドラでは、err(戻り値)がnilのときにトランザクションがコミットされます。 Commitの呼び出しでもエラーが返される可能性があるため、err = tx.Commit()を使用して戻り値をerrに設定します。 Rollbackは非nilであり、既存のエラーを上書きしたくないため、errには同じことをしません。

117
Luke