背景
sqlcでトランザクションを使うことについて、公式ドキュメントを見ていると、少し気になるところがあったので、自分なりにコードを改善してみました。
公式のコード
func bumpCounter(ctx context.Context, db *sql.DB, queries *tutorial.Queries, id int32) error {
tx, err := db.Begin()
if err != nil {
return err
}
defer tx.Rollback()
qtx := queries.WithTx(tx)
r, err := qtx.GetRecord(ctx, id)
if err != nil {
return err
}
if err := qtx.UpdateRecord(ctx, tutorial.UpdateRecordParams{
ID: r.ID,
Counter: r.Counter + 1,
}); err != nil {
return err
}
return tx.Commit()
}
tx.Rollback()
のエラーをキャッチしたい
- func bumpCounter(ctx context.Context, db *sql.DB, queries *tutorial.Queries, id int32) error {
+ func bumpCounter(ctx context.Context, db *sql.DB, queries *tutorial.Queries, id int32) (err error) {
tx, err := db.Begin()
if err != nil {
return err
}
- defer tx.Rollback()
+ defer func() {
+ err = errors.Join(err, tx.Rollback())
+ }()
qtx := queries.WithTx(tx)
r, err := qtx.GetRecord(ctx, id)
if err != nil {
return err
}
if err := qtx.UpdateRecord(ctx, tutorial.UpdateRecordParams{
ID: r.ID,
Counter: r.Counter + 1,
}); err != nil {
return err
}
return tx.Commit()
}
tx.Rollback()
のエラーをキャッチするため、名前付き戻り値を採用し、erros.Join()
を使って、tx.Rollback()
のエラーを返すようにしました。
しかしこのままでは、正常にコミットされても、tx.Rollback()
が実行されてしまい、以下のようなエラーが発生します。
sql: transaction has already been committed or rolled back
DB操作が失敗した時にのみ、tx.Rollback()
を実行する
func bumpCounter(ctx context.Context, db *sql.DB, queries *tutorial.Queries, id int32) (err error) {
tx, err := db.Begin()
if err != nil {
return err
}
- defer func() {
- err = errors.Join(err, tx.Rollback())
- }()
qtx := queries.WithTx(tx)
r, err := qtx.GetRecord(ctx, id)
if err != nil {
- return err
+ return errors.Join(err, tx.Rollback())
}
if err := qtx.UpdateRecord(ctx, tutorial.UpdateRecordParams{
ID: r.ID,
Counter: r.Counter + 1,
}); err != nil {
- return err
+ return errors.Join(err, tx.Rollback())
}
return tx.Commit()
}
上記のようにすることで、tx.Rollback()
が実行され、ロールバック時のエラーもキャッチできます。
最後に
多くのデータベースシステムでは、既にコミットされたトランザクションに対して
Rollback()
を呼び出しても、エラーは発生せず、単に無視されます。
とのことなので、別に気にしなくても良いのかもしれません。