GORM provides comprehensive transaction support for ensuring data consistency across multiple database operations.
Execute a function within a transaction. The transaction is automatically committed if no error is returned, or rolled back if an error is returned.
func (db *DB) Transaction(fc func(tx *DB) error, opts ...*sql.TxOptions) errorUsage:
err := db.Transaction(func(tx *gorm.DB) error {
// Perform database operations
if err := tx.Create(&User{Name: "Alice"}).Error; err != nil {
// Return error to rollback
return err
}
if err := tx.Create(&Order{UserID: 1, Amount: 100}).Error; err != nil {
// Return error to rollback
return err
}
// Return nil to commit
return nil
})
if err != nil {
// Transaction failed
}Manually control transaction lifecycle with Begin, Commit, and Rollback.
// Begin a transaction
func (db *DB) Begin(opts ...*sql.TxOptions) *DB
// Commit the transaction
func (db *DB) Commit() *DB
// Rollback the transaction
func (db *DB) Rollback() *DBUsage:
// Begin transaction
tx := db.Begin()
// Defer rollback in case of panic
defer func() {
if r := recover(); r != nil {
tx.Rollback()
}
}()
// Perform operations
if err := tx.Create(&User{Name: "Alice"}).Error; err != nil {
tx.Rollback()
return err
}
if err := tx.Create(&Order{UserID: 1, Amount: 100}).Error; err != nil {
tx.Rollback()
return err
}
// Commit transaction
if err := tx.Commit().Error; err != nil {
return err
}Specify transaction isolation level and other options.
import "database/sql"
// With transaction options
err := db.Transaction(func(tx *gorm.DB) error {
// Operations here
return nil
}, &sql.TxOptions{
Isolation: sql.LevelReadCommitted,
ReadOnly: false,
})
// Manual transaction with options
tx := db.Begin(&sql.TxOptions{
Isolation: sql.LevelSerializable,
ReadOnly: true,
})
defer tx.Rollback()
// ... operations
tx.Commit()Savepoints provide fine-grained transaction control, allowing you to rollback to a specific point within a transaction.
// Create a savepoint
func (db *DB) SavePoint(name string) *DB
// Rollback to a savepoint
func (db *DB) RollbackTo(name string) *DBUsage:
tx := db.Begin()
// Create first record
tx.Create(&User{Name: "Alice"})
// Create savepoint
tx.SavePoint("sp1")
// Create second record
tx.Create(&User{Name: "Bob"})
// Rollback to savepoint (Bob creation is rolled back, Alice remains)
tx.RollbackTo("sp1")
// Create third record
tx.Create(&User{Name: "Carol"})
// Commit transaction (Alice and Carol are committed)
tx.Commit()tx := db.Begin()
tx.Create(&User{Name: "Alice"})
tx.SavePoint("sp1")
tx.Create(&Order{UserID: 1})
tx.SavePoint("sp2")
tx.Create(&Item{OrderID: 1})
// Rollback to sp2 (Item creation is rolled back)
tx.RollbackTo("sp2")
// Rollback to sp1 (Order creation is also rolled back)
tx.RollbackTo("sp1")
tx.Commit() // Only Alice is committedGORM supports nested transactions using savepoints. When you call Begin within a transaction, it creates a savepoint instead of a new transaction.
db.Transaction(func(tx *gorm.DB) error {
// First level transaction
tx.Create(&User{Name: "Alice"})
// Nested transaction (uses savepoint)
return tx.Transaction(func(tx2 *gorm.DB) error {
tx2.Create(&Order{UserID: 1})
// This nested transaction can be rolled back independently
if someCondition {
return errors.New("nested transaction failed")
}
return nil
})
// If nested transaction fails, only Order is rolled back
// If we return error here, both User and Order are rolled back
})// Disable nested transactions globally
db, err := gorm.Open(sqlite.Open("gorm.db"), &gorm.Config{
DisableNestedTransaction: true,
})
// Disable for specific session
db.Session(&gorm.Session{
DisableNestedTransaction: true,
}).Transaction(func(tx *gorm.DB) error {
// Nested Begin will return error
return nil
})Use a specific connection from the pool for all operations in the callback.
func (db *DB) Connection(fc func(tx *DB) error) errorUsage:
// All operations use the same connection
err := db.Connection(func(tx *gorm.DB) error {
// All these operations share the same connection
tx.First(&user)
tx.Create(&order)
tx.Update(&user, "status", "active")
return nil
})By default, GORM runs Create, Update, Delete operations within a transaction to ensure data consistency. You can disable this for better performance.
// Disable default transaction globally
db, err := gorm.Open(sqlite.Open("gorm.db"), &gorm.Config{
SkipDefaultTransaction: true,
})
// Disable for specific operation
db.Session(&gorm.Session{
SkipDefaultTransaction: true,
}).Create(&user)Create a session with transaction settings.
tx := db.Session(&gorm.Session{
SkipDefaultTransaction: true,
DisableNestedTransaction: true,
})
// Use tx for operations
tx.Create(&user)Proper error handling in transactions.
err := db.Transaction(func(tx *gorm.DB) error {
// Create user
if err := tx.Create(&user).Error; err != nil {
return err // Rollback
}
// Validate business logic
if user.Age < 18 {
return errors.New("user must be 18 or older") // Rollback
}
// Create related record
order := Order{UserID: user.ID}
if err := tx.Create(&order).Error; err != nil {
return err // Rollback
}
// Commit
return nil
})
if err != nil {
log.Printf("Transaction failed: %v", err)
}Lifecycle hooks work within transactions.
type User struct {
gorm.Model
Name string
Age int
}
func (u *User) BeforeCreate(tx *gorm.DB) error {
// This runs within the transaction
if u.Age < 18 {
return errors.New("user must be 18 or older")
}
return nil
}
func (u *User) AfterCreate(tx *gorm.DB) error {
// This runs within the transaction
// Can create related records using tx
return tx.Create(&Notification{
UserID: u.ID,
Message: "Welcome!",
}).Error
}
// Usage
err := db.Transaction(func(tx *gorm.DB) error {
// Hooks will execute within this transaction
return tx.Create(&User{Name: "Alice", Age: 25}).Error
})For distributed transactions across multiple databases, you need to manage them manually.
// Begin transactions on multiple databases
tx1 := db1.Begin()
tx2 := db2.Begin()
defer func() {
if r := recover(); r != nil {
tx1.Rollback()
tx2.Rollback()
}
}()
// Perform operations on first database
if err := tx1.Create(&user).Error; err != nil {
tx1.Rollback()
tx2.Rollback()
return err
}
// Perform operations on second database
if err := tx2.Create(&log).Error; err != nil {
tx1.Rollback()
tx2.Rollback()
return err
}
// Commit both transactions
if err := tx1.Commit().Error; err != nil {
tx1.Rollback()
tx2.Rollback()
return err
}
if err := tx2.Commit().Error; err != nil {
// tx1 is already committed, need to handle compensation
return err
}// Good: Short transaction
db.Transaction(func(tx *gorm.DB) error {
tx.Create(&user)
tx.Create(&order)
return nil
})
// Bad: Long transaction with external I/O
db.Transaction(func(tx *gorm.DB) error {
tx.Create(&user)
// Avoid external I/O in transactions
sendEmail(user.Email) // BAD
callExternalAPI() // BAD
tx.Create(&order)
return nil
})db.Transaction(func(tx *gorm.DB) error {
// Always check errors
if err := tx.Create(&user).Error; err != nil {
return err
}
// Business logic errors should also rollback
if !isValidUser(&user) {
return errors.New("invalid user")
}
return nil
})tx := db.Begin()
defer func() {
if r := recover(); r != nil {
tx.Rollback()
}
}()
// ... operations
if err := tx.Commit().Error; err != nil {
tx.Rollback()
return err
}Pass context through transactions for cancellation and deadlines.
ctx, cancel := context.WithTimeout(context.Background(), 5*time.Second)
defer cancel()
err := db.WithContext(ctx).Transaction(func(tx *gorm.DB) error {
// Operations will respect context cancellation
tx.Create(&user)
tx.Create(&order)
return nil
})
if err == context.DeadlineExceeded {
// Handle timeout
}GORM provides interfaces for transaction operations.
// TxBeginner interface
type TxBeginner interface {
BeginTx(ctx context.Context, opts *sql.TxOptions) (*sql.Tx, error)
}
// ConnPoolBeginner interface
type ConnPoolBeginner interface {
BeginTx(ctx context.Context, opts *sql.TxOptions) (ConnPool, error)
}
// TxCommitter interface
type TxCommitter interface {
Commit() error
Rollback() error
}
// Tx interface
type Tx interface {
TxCommitter
StmtContext(ctx context.Context, stmt *sql.Stmt) *sql.Stmt
}
// SavePointerDialectorInterface for savepoint support
type SavePointerDialectorInterface interface {
SavePoint(tx *DB, name string) error
RollbackTo(tx *DB, name string) error
}These interfaces allow you to implement custom transaction behavior or use GORM with custom connection pools.