3月6日 21:37

How to use transactions in GORM?

GORM provides powerful transaction support to ensure atomicity and consistency of multiple database operations.

Basic Transaction Operations

Automatic Transactions

GORM automatically manages transactions for single operations by default:

go
// Single operation automatically uses transaction db.Create(&user) db.Save(&user) db.Delete(&user)

Manual Transactions

For scenarios requiring multiple operations, manual transaction management is needed:

go
// Begin transaction tx := db.Begin() // Execute operations if err := tx.Create(&user).Error; err != nil { // Error occurred, rollback transaction tx.Rollback() return err } if err := tx.Create(&profile).Error; err != nil { tx.Rollback() return err } // Commit transaction tx.Commit()

Transaction Methods

Begin() - Start transaction

go
tx := db.Begin()

Commit() - Commit transaction

go
tx.Commit()

Rollback() - Rollback transaction

go
tx.Rollback()

RollbackTo() - Rollback to savepoint

go
// Create savepoint tx.SavePoint("sp1") // Rollback to savepoint tx.RollbackTo("sp1")

Complete Transaction Example

Bank Transfer Example

go
func TransferMoney(db *gorm.DB, fromID, toID uint, amount float64) error { tx := db.Begin() defer func() { if r := recover(); r != nil { tx.Rollback() } }() // Query source account var fromAccount Account if err := tx.Where("id = ?", fromID).First(&fromAccount).Error; err != nil { tx.Rollback() return err } // Check if balance is sufficient if fromAccount.Balance < amount { tx.Rollback() return errors.New("Insufficient balance") } // Deduct from source account if err := tx.Model(&fromAccount).Update("balance", fromAccount.Balance-amount).Error; err != nil { tx.Rollback() return err } // Query destination account var toAccount Account if err := tx.Where("id = ?", toID).First(&toAccount).Error; err != nil { tx.Rollback() return err } // Add to destination account if err := tx.Model(&toAccount).Update("balance", toAccount.Balance+amount).Error; err != nil { tx.Rollback() return err } // Record transaction log transaction := Transaction{ FromAccountID: fromID, ToAccountID: toID, Amount: amount, Status: "completed", } if err := tx.Create(&transaction).Error; err != nil { tx.Rollback() return err } // Commit transaction return tx.Commit().Error }

Transaction Callbacks

GORM provides transaction callback methods to simplify transaction handling:

Transaction() Method

go
err := db.Transaction(func(tx *gorm.DB) error { // Execute operations in transaction if err := tx.Create(&user).Error; err != nil { // Returning error automatically rolls back return err } if err := tx.Create(&profile).Error; err != nil { return err } // Returning nil automatically commits return nil }) if err != nil { // Transaction failed }

Nested Transactions

go
err := db.Transaction(func(tx *gorm.DB) error { if err := tx.Create(&user).Error; err != nil { return err } // Nested transaction return tx.Transaction(func(tx2 *gorm.DB) error { return tx2.Create(&profile).Error }) })

Transaction Options

Set Transaction Isolation Level

go
tx := db.Begin(&sql.TxOptions{ Isolation: sql.LevelSerializable, })

Read-only Transaction

go
tx := db.Begin(&sql.TxOptions{ ReadOnly: true, })

Common Operations in Transactions

Create Record

go
tx.Create(&user)

Update Record

go
tx.Model(&user).Update("name", "John")

Delete Record

go
tx.Delete(&user)

Query Record

go
tx.First(&user, 1)

Raw SQL

go
tx.Exec("UPDATE users SET name = ?", "John")

Transaction Error Handling

Check Transaction Status

go
tx := db.Begin() defer func() { if r := recover(); r != nil { tx.Rollback() } }() // Execute operations if err := tx.Create(&user).Error; err != nil { tx.Rollback() log.Printf("Transaction failed: %v", err) return err } // Commit transaction if err := tx.Commit().Error; err != nil { log.Printf("Commit failed: %v", err) return err }

Transaction Timeout Handling

go
ctx, cancel := context.WithTimeout(context.Background(), 5*time.Second) defer cancel() tx := db.BeginTx(ctx, nil) // Execute operations...

Nested Transactions and Savepoints

Create Savepoint

go
tx.SavePoint("sp1")

Rollback to Savepoint

go
tx.RollbackTo("sp1")

Release Savepoint

go
tx.Exec("RELEASE SAVEPOINT sp1")

Notes

  1. Transaction Scope: Transactions should be used in the smallest possible scope to reduce lock time
  2. Error Handling: Must properly handle errors in transactions, ensure rollback on failure
  3. Resource Release: Use defer to ensure transactions are properly handled when function exits
  4. Isolation Level: Choose appropriate transaction isolation level based on business requirements
  5. Deadlock Prevention: Avoid holding locks for long periods, access resources in fixed order
  6. Performance Considerations: Transactions add database overhead, avoid unnecessary transactions

Best Practices

  1. Use Transaction() Callback: Simplifies transaction handling code
  2. Keep Short: Transactions should be as short and fast as possible
  3. Error Handling: Always check and handle errors
  4. Logging: Log transaction start, commit, and rollback
  5. Test Coverage: Write comprehensive test cases for transaction logic
  6. Avoid Nesting: Try to avoid overly deep nested transactions

Transaction Isolation Levels

Read Uncommitted

go
tx := db.Begin(&sql.TxOptions{ Isolation: sql.LevelReadUncommitted, })

Read Committed

go
tx := db.Begin(&sql.TxOptions{ Isolation: sql.LevelReadCommitted, })

Repeatable Read

go
tx := db.Begin(&sql.TxOptions{ Isolation: sql.LevelRepeatableRead, })

Serializable

go
tx := db.Begin(&sql.TxOptions{ Isolation: sql.LevelSerializable, })
标签:Gorm