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¶
- Keep transactions short - Long transactions hold resources and increase conflict potential
- Use auto-commit for single operations - No need for explicit transactions
- Always commit or rollback - Don't leave transactions hanging
- Handle errors properly - Call
rollback()when operations fail