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, })