Skip to content

Transactions

Grafeo supports ACID transactions with snapshot isolation.

Auto-Commit Mode

By default, each query runs in auto-commit mode:

# Each execute is automatically committed
db.execute("INSERT (:Person {name: 'Alix'})")
db.execute("INSERT (:Person {name: 'Gus'})")

Explicit Transactions

For multiple operations in a single atomic transaction, use begin_transaction():

# Start an explicit transaction
with db.begin_transaction() as tx:
    tx.execute("INSERT (:Person {name: 'Alix'})")
    tx.execute("INSERT (:Person {name: 'Gus'})")
    tx.commit()  # Both inserts committed atomically

Transaction Context Manager

The transaction context manager provides automatic commit on success:

with db.begin_transaction() as tx:
    tx.execute("INSERT (:Person {name: 'Alix'})")
    tx.execute("INSERT (:Person {name: 'Gus'})")
    tx.commit()
# If no exception, transaction is committed

Rollback

Discard all changes in a transaction:

with db.begin_transaction() as tx:
    tx.execute("INSERT (:Person {name: 'Alix'})")

    # Decide to rollback
    tx.rollback()
    # Alix was not created

Rollback on Error

When an exception occurs, call rollback() to discard changes:

with db.begin_transaction() as tx:
    try:
        tx.execute("INSERT (:Person {name: 'Alix'})")
        tx.execute("INSERT (:Person {name: 'Gus'})")
        raise ValueError("Something went wrong")
        tx.commit()
    except ValueError:
        tx.rollback()  # Both inserts discarded

SPARQL Transactions

SPARQL operations also support transactions:

with db.begin_transaction() as tx:
    tx.execute_sparql("""
        INSERT DATA {
            <http://example.org/alix> <http://xmlns.com/foaf/0.1/name> "Alix" .
        }
    """)
    tx.commit()

# Or rollback SPARQL changes
with db.begin_transaction() as tx:
    tx.execute_sparql("""
        INSERT DATA {
            <http://example.org/gus> <http://xmlns.com/foaf/0.1/name> "Gus" .
        }
    """)
    tx.rollback()  # Gus was not created

Snapshot Isolation

Grafeo uses snapshot isolation by default. Each transaction sees a consistent snapshot of the database at the time it started:

# Transaction 1 begins
tx1 = db.begin_transaction()

# Transaction 2 makes changes and commits
with db.begin_transaction() as tx2:
    tx2.execute("INSERT (:Person {name: 'NewPerson'})")
    tx2.commit()

# Transaction 1 still sees old data (snapshot isolation)
result = tx1.execute("MATCH (p:Person) RETURN count(p)")
# Does not include NewPerson

tx1.commit()

Best Practices

  1. Keep transactions short - Long transactions hold resources and increase conflict potential
  2. Use auto-commit for single operations - No need for explicit transactions
  3. Always commit or rollback - Don't leave transactions hanging
  4. Handle errors properly - Call rollback() when operations fail