Interactive transactions let you group multiple operations into an atomic unit. Unlike text-based transactions (BEGIN TRANSACTION; ... COMMIT; — see the query builder for composing those), interactive transactions allow you to execute statements one at a time, inspect results, and conditionally decide whether to commit or cancel.
Transactions require a WebSocket connection (ws:// or wss://) and SurrealDB v3 or later.
API references
Method | Description |
|---|---|
db.Begin(ctx) | Starts a transaction on the default session |
session.Begin(ctx) | Starts a transaction within a session |
tx.Commit(ctx) | Commits the transaction, applying all changes |
tx.Cancel(ctx) | Cancels the transaction, discarding all changes |
tx.ID() | Returns the transaction's UUID |
tx.IsClosed() | Returns whether the transaction has been committed or canceled |
Starting a transaction
Call .Begin() on a *DB or *Session to start a transaction. The transaction inherits the authentication and namespace context from the connection or session that started it.
tx, err := db.Begin(ctx)
if err != nil {
log.Fatal(err)
}
defer tx.Cancel(ctx) Always use defer tx.Cancel(ctx) immediately after .Begin(). If the transaction has already been committed, .Cancel() returns ErrTransactionClosed but does not cause any harm.
Executing operations within a transaction
Transactions satisfy the sendable constraint, so all generic functions like Query, Select, Create, Update, and Delete accept a *Transaction.
tx, err := db.Begin(ctx)
if err != nil {
log.Fatal(err)
}
defer tx.Cancel(ctx)
_, err = surrealdb.Create[any](ctx, tx, models.Table("accounts"), map[string]any{
"name": "Alice",
"balance": 1000,
})
if err != nil {
log.Fatal(err)
}
_, err = surrealdb.Create[any](ctx, tx, models.Table("accounts"), map[string]any{
"name": "Bob",
"balance": 500,
})
if err != nil {
log.Fatal(err)
}
if err := tx.Commit(ctx); err != nil {
log.Fatal(err)
}Changes made within a transaction are not visible to other connections or sessions until the transaction is committed.
Conditional commit or cancel
Because interactive transactions let you inspect results between operations, you can decide whether to commit based on runtime conditions.
tx, err := db.Begin(ctx)
if err != nil {
log.Fatal(err)
}
defer tx.Cancel(ctx)
results, err := surrealdb.Query[[]map[string]any](ctx, tx,
"SELECT * FROM accounts WHERE name = 'Alice'",
nil,
)
if err != nil {
log.Fatal(err)
}
balance, ok := (*results)[0].Result[0]["balance"].(float64)
if !ok || balance < 100 {
fmt.Println("Insufficient balance, canceling")
return
}
_, err = surrealdb.Query[[]any](ctx, tx,
"UPDATE accounts SET balance -= 100 WHERE name = 'Alice'; UPDATE accounts SET balance += 100 WHERE name = 'Bob';",
nil,
)
if err != nil {
log.Fatal(err)
}
if err := tx.Commit(ctx); err != nil {
log.Fatal(err)
}Transaction limitations
Transactions do not support session state changes. The following operations are not available on a *Transaction:
The namespace, database, authentication, and variables are inherited from the *DB or *Session that started the transaction.
Learn more
Transaction API reference for complete method signatures
Multiple sessions for session-scoped transactions
Error handling for transaction error types
SurrealQL transactions for text-based transaction syntax