Always use database transactions when performing operations that require atomicity or could create race conditions. This includes scenarios where you check for existence before creating/updating records, or when multiple related database operations must succeed or fail together.
Follow the standard transaction pattern:
BeginTx()
Example:
func (store *store) Update(ctx context.Context, funnel *traceFunnels.StorableFunnel) error {
tx, err := store.sqlstore.BunDB().BeginTx(ctx, nil)
if err != nil {
return err
}
defer tx.Rollback()
// Check if funnel exists
exists, err := tx.NewSelect().
Model((*traceFunnels.StorableFunnel)(nil)).
Where("name = ? AND org_id = ? AND id != ?", funnel.Name, funnel.OrgID, funnel.ID).
Exists(ctx)
if err != nil {
return err
}
if exists {
return errors.NewAlreadyExistsf("funnel with name already exists")
}
// Update the funnel
_, err = tx.NewUpdate().
Model(funnel).
Where("id = ?", funnel.ID).
Exec(ctx)
if err != nil {
return err
}
return tx.Commit()
}
This prevents race conditions where another process could create a conflicting record between the existence check and the update operation.
Enter the URL of a public GitHub repository