Ultipa GQLDB provides full ACID transaction support with snapshot isolation, savepoints, and read-your-own-writes consistency.
Transactions group multiple read/write operations into a single atomic unit. Either all operations succeed (commit) or none of them take effect (rollback).
| Property | Behavior |
|---|---|
| Atomicity | All changes are applied together on commit, or none at all on rollback. |
| Consistency | The database remains in a valid state after each transaction — data integrity rules are enforced at commit time. |
| Isolation | Each transaction sees a consistent snapshot from the moment it started. |
| Durability | Committed data is persisted to storage. |
NOTEWithout an explicit transaction, each write takes effect immediately — there is no implicit transaction wrapping your queries. Use transactions when you need to group multiple operations atomically, require rollback capability, or need snapshot isolation.
Starts a new transaction. Returns a transaction_id and status.
GQL-- Read-write transaction (default) START TRANSACTION -- Equivalent BEGIN TRANSACTION -- Read-only transaction (all write operations are rejected) START TRANSACTION READ ONLY
Once START TRANSACTION is executed, all subsequent queries run within that transaction until an explicit COMMIT or ROLLBACK is issued. There is no need to pass a transaction handle — every query automatically participates in the active transaction.
Applies all buffered operations atomically to storage.
GQLCOMMIT
Discards all changes in the current transaction.
GQLROLLBACK
Creates a named snapshot within the transaction. You can later roll back to this point without discarding the entire transaction.
GQLSAVEPOINT my_savepoint
Rolls back all operations performed after the named savepoint was created. The savepoint itself is retained and can be rolled back to again.
GQLROLLBACK TO SAVEPOINT my_savepoint
Releases a savepoint, keeping all changes made since it was created. The savepoint can no longer be rolled back to.
GQLRELEASE SAVEPOINT my_savepoint
Lists all active transactions with their metadata.
GQLSHOW TRANSACTIONS
Returns columns: transaction_id, status, read_only, start_time.
Forcibly terminates a running transaction by ID.
GQLSTOP TRANSACTION 'tx_abc123' -- Equivalent KILL TRANSACTION 'tx_abc123'
How is this different from COMMIT / ROLLBACK?
COMMIT and ROLLBACK act on your current transaction automatically — you don't need to specify an ID.STOP TRANSACTION requires an explicit transaction ID, so you can use it to terminate any transaction — including ones you don't own.ROLLBACK). Nothing from the stopped transaction is saved.SHOW TRANSACTIONS to find a stuck transaction's ID, then STOP TRANSACTION '<id>' to kill it.A transaction can span multiple statements. You can either send each statement as a separate query call, or combine them into a single semicolon-separated string. Both approaches are equivalent — semicolons are only needed when packing multiple statements into one query.
Separate queries (no semicolons):
GQLSTART TRANSACTION INSERT (:Person {_id: 'alice', name: 'Alice'}) INSERT (:Person {_id: 'bob', name: 'Bob'}) MATCH (a:Person WHERE a._id = 'alice'), (b:Person WHERE b._id = 'bob') INSERT (a)-[:KNOWS]->(b) COMMIT
Single query (semicolons as delimiters):
GQLSTART TRANSACTION; INSERT (:Person {_id: 'alice', name: 'Alice'}); INSERT (:Person {_id: 'bob', name: 'Bob'}); MATCH (a:Person WHERE a._id = 'alice'), (b:Person WHERE b._id = 'bob') INSERT (a)-[:KNOWS]->(b); COMMIT
When a transaction begins, the database captures a point-in-time snapshot. All reads within the transaction see data as it was at that moment, regardless of what other transactions do afterward.
This means:
Within a transaction, you can immediately read data you just wrote. This is critical for patterns like inserting nodes and then creating edges between them:
GQLSTART TRANSACTION INSERT (:Person {_id: 'alice', name: 'Alice'}) INSERT (:Person {_id: 'bob', name: 'Bob'}) -- MATCH can see the nodes just inserted above MATCH (a:Person WHERE a._id = 'alice'), (b:Person WHERE b._id = 'bob') INSERT (a)-[:KNOWS]->(b) COMMIT
When reading data inside a transaction, the database checks in this order:
Savepoints create named snapshots within a transaction. This enables partial rollback without discarding the entire transaction.
GQLSTART TRANSACTION INSERT (:Person {_id: 'p1', name: 'Alice'}) SAVEPOINT sp1 -- snapshot: {Alice} INSERT (:Person {_id: 'p2', name: 'Bob'}) SAVEPOINT sp2 -- snapshot: {Alice, Bob} INSERT (:Person {_id: 'p3', name: 'Charlie'}) ROLLBACK TO SAVEPOINT sp1 -- restore to {Alice}, sp2 invalidated -- Only Alice exists now COMMIT
Savepoint rules:
ROLLBACK TO SAVEPOINT sp1, sp1 is still available for another rollback.RELEASE SAVEPOINT sp1 keeps changes and frees the snapshot memory.| Setting | Default | Description |
|---|---|---|
| Max transactions | 10,000 | Maximum concurrent transactions before rejection |
| Transaction TTL | 1 hour | Auto-terminate transactions older than this |
| Cleanup interval | 1 minute | How often the database checks for expired transactions |
| Status | Meaning |
|---|---|
active | Transaction is running |
committed | Transaction was committed successfully |
rolledback | Transaction was rolled back |
stopped | Transaction was forcibly terminated via STOP/KILL |
timeout | Transaction exceeded its TTL and was auto-terminated |
GQL-- List all transactions SHOW TRANSACTIONS -- Force-stop a specific transaction STOP TRANSACTION 'tx_abc123'
READ ONLY transactions reject all write operations.GQLSTART TRANSACTION INSERT (:Person {_id: 'alice', name: 'Alice', age: 30}) INSERT (:Person {_id: 'bob', name: 'Bob', age: 25}) -- Read-your-own-writes: MATCH sees the uncommitted inserts MATCH (p:Person) RETURN p.name, p.age COMMIT
GQLSTART TRANSACTION INSERT (:Person {_id: 'alice', name: 'Alice'}) INSERT (:Person {_id: 'bob', name: 'Bob'}) MATCH (a:Person WHERE a._id = 'alice'), (b:Person WHERE b._id = 'bob') INSERT (a)-[:KNOWS]->(b) COMMIT
GQLSTART TRANSACTION INSERT (:Person {_id: 'p1', name: 'Alice'}) SAVEPOINT before_bob INSERT (:Person {_id: 'p2', name: 'Bob'}) INSERT (:Person {_id: 'p3', name: 'Charlie'}) -- Oops, undo Bob and Charlie ROLLBACK TO SAVEPOINT before_bob -- Only Alice will be committed COMMIT
GQLSTART TRANSACTION INSERT (:Person {_id: 'p1', name: 'Alice'}) INSERT (:Person {_id: 'p2', name: 'Bob'}) -- Changed my mind, discard everything ROLLBACK
GQL-- See what's running SHOW TRANSACTIONS -- Kill a hung transaction STOP TRANSACTION 'tx_550e8400-e29b-41d4-a716-446655440000'