A transaction object runs operations in one client-side transaction on the server. The changes that its methods make are applied when the transaction commits, and discarded when it is cancelled. BlockingSurrealTransaction is the class on a blocking connection, and AsyncSurrealTransaction the class on an asynchronous connection.
Client-side transactions need a WebSocket connection (ws:// or wss://) and SurrealDB 3.0 or later. Transactions explains how to use them, and how to use batch() on other connections.
Creating a transaction
Begin a transaction on the session of the connection, or on a session from new_session().
db.begin_transaction(session_id=None)
session.begin_transaction()Parameter | Type | Description |
|---|---|---|
session_id | UUID | None | The id of the session to begin the transaction on. Defaults to the session of the connection. |
Returns: a BlockingSurrealTransaction on a blocking connection. On an asynchronous connection, begin_transaction() is a coroutine that returns an AsyncSurrealTransaction.
Against SurrealDB 2.x, begin_transaction() raises UnavailableFeatureError. HTTP connections have no begin_transaction() method, and embedded connections raise UnsupportedFeatureError.
Examples
txn = db.begin_transaction()
session = db.new_session()
session.use("surrealdb", "docs")
session_txn = session.begin_transaction()txn = await db.begin_transaction()
session = await db.new_session()
await session.use("surrealdb", "docs")
session_txn = await session.begin_transaction()Context manager
A transaction is a context manager. The transaction commits when the block finishes, and it is cancelled when an exception leaves the block. The exception then continues to your code. A call to commit() or cancel() inside the block ends the transaction, and the end of the block then does nothing more.
with db.begin_transaction() as txn:
txn.query("UPDATE account:tobie SET balance -= 20").execute()
txn.query("UPDATE account:jaime SET balance += 20").execute()async with await db.begin_transaction() as txn:
await txn.query("UPDATE account:tobie SET balance -= 20")
await txn.query("UPDATE account:jaime SET balance += 20")A statement that fails inside the block raises its error, but it does not end the transaction. If you catch the error inside the block, the transaction commits at the end of the block.
Methods shared with the connection
A transaction has these methods of the connection. They take the same arguments, without session_id= and txn_id=, and the queries and CRUD operations that they send are part of the transaction. On an asynchronous transaction, await each method except .timeout(), and await the methods of .files.
| Method | Returns | Description |
|---|---|---|
.query(query, vars=None) | Query builder | Runs SurrealQL statements in the transaction. Its .execute() is not streamed here, and its .rows() is, so read a stream to the end before you commit. |
.query_raw(query, vars=None) | dict[str, Any] | Runs SurrealQL statements and returns the raw response, with the status of each statement. |
.select(record, *, fields=None, into=None) | CRUD builder | Reads records. |
.create(record, data=..., *, into=None) | CRUD builder, or the record | Creates a record. |
.update(record, data=..., *, into=None) | CRUD builder, or the result | Replaces records, or changes them with .merge() or .patch(). |
.upsert(record, data=..., *, into=None) | CRUD builder, or the result | Creates a record, or changes it when it exists. |
.delete(record, *, into=None) | CRUD builder | Deletes records. |
.insert(table, data=..., *, into=None, relation=False) | Insert builder, or the records | Inserts records, or edge records with relation=True. |
.relate(from_, edge, to, data=...) | CRUD builder, or the edge | Connects records with an edge record. |
.run(name, args=None, version=None) | Value | Calls a function in the transaction. |
.api(path, *, method="get", body=None, headers=None, query=None) | ApiResponse | Calls a DEFINE API endpoint in the transaction. |
.let(key, value) | None | Defines a variable on the session of the transaction. The variable stays after the transaction ends, also after cancel(). |
.set(key, value) | None | Another name for .let(). |
.unset(key) | None | Removes a variable. |
.info() | Value | Returns the record of the signed-in record user, or None. |
.version() | str | Returns the version of the server. |
.files | BlockingFiles or AsyncFiles | Reads and writes files in a bucket. A file write is not part of the transaction: it applies at once, and it stays when the transaction is cancelled. |
.timeout(seconds) | Context manager | Sets how long the client waits for each reply inside a with block. |
On a blocking transaction, a write that you give its data runs at once and returns the result, as on the connection. A transaction has no batch(), gql(), graphql() or live query methods.
Transaction-specific methods
.commit()
Applies the changes of the transaction and ends it.
txn.commit()Returns: None
When another transaction has changed the same records first, commit() raises QueryError with is_transaction_conflict set to True, and none of the changes are applied. A commit that fails also ends the transaction. After the transaction has ended, every method of the object raises ValidationError.
Examples
txn = db.begin_transaction()
txn.update(RecordID("account", "tobie")).merge({"balance": 80})
txn.update(RecordID("account", "jaime")).merge({"balance": 70})
txn.commit()txn = await db.begin_transaction()
await txn.update(RecordID("account", "tobie")).merge({"balance": 80})
await txn.update(RecordID("account", "jaime")).merge({"balance": 70})
await txn.commit() .cancel()
Discards the changes of the transaction and ends it. After cancel(), every method of the object raises ValidationError.
txn.cancel()Returns: None
Examples
txn = db.begin_transaction()
try:
txn.query("UPDATE account:tobie SET balance -= 20").execute()
txn.query("UPDATE account:jaime SET balance += 20").execute()
except Exception:
txn.cancel()
raise
txn.commit()txn = await db.begin_transaction()
try:
await txn.query("UPDATE account:tobie SET balance -= 20")
await txn.query("UPDATE account:jaime SET balance += 20")
except Exception:
await txn.cancel()
raise
await txn.commit()Keep commit() out of the try block. A commit that fails has already ended the transaction, so a cancel() after it raises ValidationError and hides the error of the commit.
Complete example
This example moves an amount from one account to another in a transaction. The second transfer is larger than the balance, so the THROW cancels its transaction, and neither account changes.
from surrealdb import RecordID, ServerError, Surreal, Table
def transfer(db, amount):
try:
with db.begin_transaction() as txn:
txn.query(
"UPDATE account:tobie SET balance -= $amount",
{"amount": amount},
).execute()
txn.query(
"IF account:tobie.balance < 0 { THROW 'Insufficient funds' }"
).execute()
txn.query(
"UPDATE account:jaime SET balance += $amount",
{"amount": amount},
).execute()
print(f"Transferred {amount}")
except ServerError as e:
print(f"Transfer of {amount} cancelled: {e}")
with Surreal("ws://localhost:8000") as db:
db.signin({"username": "root", "password": "secret"})
db.use("surrealdb", "docs")
db.upsert(RecordID("account", "tobie"), {"balance": 100})
db.upsert(RecordID("account", "jaime"), {"balance": 50})
transfer(db, 30)
transfer(db, 500)
for account in db.select(Table("account")).execute():
print(account["id"], account["balance"])Transferred 30
Transfer of 500 cancelled: An error occurred: Insufficient funds
account:jaime 80
account:tobie 70See also
Transactions for a guide to transactions and
batch()Surreal for the methods of the connection
SurrealSession for sessions
Builders for the builders that the methods return
Errors for the error classes