UltipaDocs
Products
Solutions
Resources
Company
Start Free Trial
UltipaDocs
Start Free Trial
  • Introduction
  • GQL vs Other Languages
    • Overview
    • Node and Edge Patterns
    • Path Patterns
    • Quantified Paths
    • Questioned Paths
    • Shortest Paths
    • Cheapest Paths
    • K-Hop Traversal
    • Graph Patterns
    • Overview
    • Open Graphs
    • Closed Graphs
    • Graph Types
    • Constraints
    • Projections
    • Storage Maintenance
    • Node and Edge IDs
    • INSERT
    • INSERT OVERWRITE
    • UPSERT
    • MERGE
    • SET
    • REMOVE
    • DELETE
    • FOREACH
    • LOAD CSV
    • Query Composition
    • Result Table and Visualization
    • MATCH
    • OPTIONAL MATCH
    • FILTER
    • LET
    • FOR
    • ORDER BY
    • LIMIT
    • SKIP
    • CALL
    • RETURN
    • Composite Query
    • NEXT
    • All Functions
    • Element Functions
    • Path Functions
    • Aggregate Functions
    • Mathematical Functions
    • Trigonometric Functions
    • String Functions
    • List Functions
    • Datetime Functions
    • Spatial Functions
    • Null Functions
    • Utility Functions
    • Type Conversion Functions
    • Table Functions
  • Operators
  • Predicates
    • Overview
    • CASE
    • LET Value Expression
    • Value Query Expression
    • Count Query Expression
    • List Expressions
    • Current Values
    • Index
    • Full-text Index
    • Vector Index
  • Transactions
  • Triggers
  • Query Management
  • Execution Plan
    • Variables
    • Values and Types
    • Comments
    • Reserved Words
    • Naming Conventions
    • Syntactic Notation
  • GQL Conformance
  1. Docs
  2. /
  3. ISO GQL

Transactions

Ultipa GQLDB provides full ACID transaction support with snapshot isolation, savepoints, and read-your-own-writes consistency.

Overview

Transactions group multiple read/write operations into a single atomic unit. Either all operations succeed (commit) or none of them take effect (rollback).

PropertyBehavior
AtomicityAll changes are applied together on commit, or none at all on rollback.
ConsistencyThe database remains in a valid state after each transaction — data integrity rules are enforced at commit time.
IsolationEach transaction sees a consistent snapshot from the moment it started.
DurabilityCommitted data is persisted to storage.
NOTE

Without an explicit transaction, each statement runs in a transaction of its own. INSERT, SET, REMOVE, DELETE and MERGE are atomic on their own: if any element fails, the statement is rolled back as a whole and nothing is applied, and the outcome does not depend on the order the elements were matched in. Use an explicit transaction when you need to group several statements atomically, require rollback capability, or need snapshot isolation.

Three things stay outside it. UPSERT and INSERT OVERWRITE are excluded, because relocating an edge's endpoints works only outside a transaction; so are requests that switch graphs (USE g INSERT ...), since a graph cannot be switched inside a transaction; and bulk-import sessions are a separate path, unaffected. A request joining segments with NEXT is covered.

An auto-commit statement never reports a write conflict. Conflict detection is a property of transactions you open yourself, where you have something to retry with; an auto-commit statement waits for a contended key instead.

Setting the environment variable GQLDB_IMPLICIT_TX=off restores the previous element-by-element behavior, where a statement could fail with part of its work already applied.

Transaction Limits

LimitValueDescription
Max concurrent transactions10,000New transactions are rejected when the limit is reached
Max transactions per connection1Only one active transaction per connection; use savepoints for partial rollback
Transaction timeout1 hourTransactions older than this are auto-terminated

Transaction Control

START TRANSACTION

Starts a new transaction. Returns a transaction_id and status.

GQL
-- Read-write transaction (default)
START TRANSACTION

-- Equivalent
BEGIN TRANSACTION

-- Read-only transaction
BEGIN TRANSACTION READ ONLY

A read-only transaction provides snapshot isolation for reads — all queries within the transaction see the same consistent point-in-time view of the data, even if other transactions are writing concurrently. Any write operation is rejected. This is useful for reports or analytics across multiple queries where you need consistent data throughout.

Once a transaction is started, 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.

COMMIT

Applies all buffered operations atomically to storage.

GQL
COMMIT
NOTE

A successful COMMIT can carry a warning — show it, and do not retry on it. On a graph with EDGE_ID enabled, the lookup of edges by _id is written after the transaction's changes are stored. If that write fails, the transaction is already committed, so the COMMIT succeeds and a warning says the lookup could not be written and that the transaction must not be run again. Retrying it would store its edges a second time.

The warning reaches every path: the result's warnings for the COMMIT statement, for an auto-commit statement and for a CALL of a procedure with an ATOMIC block; Tx.Warnings() after Tx.Commit, which returns no error; and, over gRPC, the commit response's message with success true.

The database records that the lookup is behind and rebuilds it from the stored edges the next time it opens, so the edges answer to their _id after a restart. Until then RETURN db.validate_graph() AS health reports index_rebuild_pending under edge_id_cache_drift.

ROLLBACK

Discards all changes in the current transaction.

GQL
ROLLBACK

SAVEPOINT

Creates a named snapshot within the transaction. You can later roll back to this point without discarding the entire transaction.

GQL
SAVEPOINT my_savepoint

ROLLBACK TO SAVEPOINT

Rolls back all operations performed after the named savepoint was created. The savepoint itself is retained and can be rolled back to again.

GQL
ROLLBACK TO SAVEPOINT my_savepoint

RELEASE SAVEPOINT

Releases a savepoint, keeping all changes made since it was created. The savepoint can no longer be rolled back to.

GQL
RELEASE SAVEPOINT my_savepoint

SHOW TRANSACTIONS

Lists all active transactions across all connections.

GQL
SHOW TRANSACTIONS

Returns columns: transaction_id, status, read_only, start_time.

STOP TRANSACTION

Forcibly terminates a running transaction by ID. You can run SHOW TRANSACTIONS to find a transaction's ID.

GQL
STOP TRANSACTION tx_abc123

START TRANSACTION and KILL TRANSACTION are equivalent.

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.
  • All uncommitted changes are discarded (same as ROLLBACK).

RESET TRANSACTIONS

An administrative escape hatch that terminates every active transaction at once, equivalent to running STOP TRANSACTION on each one. Each transaction is rolled back (uncommitted changes discarded) and ended; none are left open. Useful for clearing a wedged state.

GQL
RESET TRANSACTIONS

-- Singular alias, same effect
RESET TRANSACTION

It returns one row per affected transaction with transaction_id and result (stopped, or failed: <reason>). Use it sparingly: it terminates other sessions' in-flight work without warning, discarding all their uncommitted changes.

Multi-Statement Transactions

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):

GQL
START 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):

GQL
START 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

Statement Failure Inside a Transaction

When a statement fails inside an explicit transaction, the transaction is left open in an aborted state. It holds no partial work, and it must be ended with ROLLBACK:

After a failed statementResult
Any further statementRefused
COMMITRefused
ROLLBACKSucceeds, and is how you end the transaction
GQL
START TRANSACTION
INSERT (:Person {_id: 'alice', name: 'Alice'})
INSERT (:Person {_id: 'alice', name: 'Duplicate'})   -- fails
INSERT (:Person {_id: 'carol', name: 'Carol'})       -- refused: the transaction is aborted
COMMIT                                               -- refused
ROLLBACK                                             -- ends the transaction

An application that keeps issuing statements after one fails must issue ROLLBACK and retry the transaction. Statements sent after a failure are refused rather than applied, so nothing sent after the failure reaches storage.

Isolation and Consistency

Snapshot Isolation

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:

  • Your queries always return consistent results throughout the transaction.
  • Other transactions' uncommitted (or even committed) changes won't suddenly appear in your reads.
  • Two transactions running at the same time won't interfere with each other's reads.

Read-Your-Own-Writes (RYOW)

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:

GQL
START 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:

  1. Pending changes in the current transaction (inserts, updates, deletes not yet committed)
  2. Snapshot cache (data already read once in this transaction)
  3. Storage (persisted data from before the transaction started)

Write Conflicts

If two transactions read the same node and then both modify it, the one that commits second is refused at COMMIT with error 3011:

GQL
-- Session A
START TRANSACTION
MATCH (p:Person {_id: 'alice'}) SET p.value = 1
-- (Session B commits its own change to alice here)
COMMIT
--   [3011] write conflict: node "…" was modified by another transaction after this one
--   read it; re-read it and retry

This is a normal, retryable outcome, not a database failure. Re-read the values and run the transaction again. Over gRPC it arrives as the status Aborted, which is the standard "retry the transaction" status.

NOTE

3011 is retryable; 3010 is not. 3011 is the write conflict above. The other transaction errors under 3010 — a nested START TRANSACTION, a rollback of a transaction that has already ended — are caller mistakes, and retrying them will not help. Match on the code rather than the message text.

An auto-commit statement never reports a write conflict. Conflict detection belongs to transactions you open yourself, where you have something to retry with; an auto-commit statement waits for a contended key instead.

Savepoints

Savepoints create named snapshots within a transaction. This enables partial rollback without discarding the entire transaction.

GQL
START 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:

  • Ordering: Savepoints created after a rolled-back savepoint are automatically invalidated.
  • Reuse: After ROLLBACK TO SAVEPOINT sp1, sp1 is still available for another rollback.
  • Release: RELEASE SAVEPOINT sp1 keeps changes and frees the snapshot memory.
  • Nesting: Multiple savepoints can be created at different points in the transaction.