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
gotx := db.Begin()
Commit() - Commit transaction
gotx.Commit()
Rollback() - Rollback transaction
gotx.Rollback()
RollbackTo() - Rollback to savepoint
go// Create savepoint tx.SavePoint("sp1") // Rollback to savepoint tx.RollbackTo("sp1")
Complete Transaction Example
Bank Transfer Example
gofunc 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
goerr := 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
goerr := 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
gotx := db.Begin(&sql.TxOptions{ Isolation: sql.LevelSerializable, })
Read-only Transaction
gotx := db.Begin(&sql.TxOptions{ ReadOnly: true, })
Common Operations in Transactions
Create Record
gotx.Create(&user)
Update Record
gotx.Model(&user).Update("name", "John")
Delete Record
gotx.Delete(&user)
Query Record
gotx.First(&user, 1)
Raw SQL
gotx.Exec("UPDATE users SET name = ?", "John")
Transaction Error Handling
Check Transaction Status
gotx := 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
goctx, cancel := context.WithTimeout(context.Background(), 5*time.Second) defer cancel() tx := db.BeginTx(ctx, nil) // Execute operations...
Nested Transactions and Savepoints
Create Savepoint
gotx.SavePoint("sp1")
Rollback to Savepoint
gotx.RollbackTo("sp1")
Release Savepoint
gotx.Exec("RELEASE SAVEPOINT sp1")
Notes
- Transaction Scope: Transactions should be used in the smallest possible scope to reduce lock time
- Error Handling: Must properly handle errors in transactions, ensure rollback on failure
- Resource Release: Use defer to ensure transactions are properly handled when function exits
- Isolation Level: Choose appropriate transaction isolation level based on business requirements
- Deadlock Prevention: Avoid holding locks for long periods, access resources in fixed order
- Performance Considerations: Transactions add database overhead, avoid unnecessary transactions
Best Practices
- Use Transaction() Callback: Simplifies transaction handling code
- Keep Short: Transactions should be as short and fast as possible
- Error Handling: Always check and handle errors
- Logging: Log transaction start, commit, and rollback
- Test Coverage: Write comprehensive test cases for transaction logic
- Avoid Nesting: Try to avoid overly deep nested transactions
Transaction Isolation Levels
Read Uncommitted
gotx := db.Begin(&sql.TxOptions{ Isolation: sql.LevelReadUncommitted, })
Read Committed
gotx := db.Begin(&sql.TxOptions{ Isolation: sql.LevelReadCommitted, })
Repeatable Read
gotx := db.Begin(&sql.TxOptions{ Isolation: sql.LevelRepeatableRead, })
Serializable
gotx := db.Begin(&sql.TxOptions{ Isolation: sql.LevelSerializable, })