Chapter 11 · Transactions, Isolation, Locking, Deadlocks, Retries, and Consistency

ACID Transactions, Auto-Commit vs Explicit Transactions, Statement Boundaries, and Failure Atomicity

Define AtlasMart write units so atomicity, isolation and ambiguous outcomes are visible and testable rather than implicit.

Advanced145–180 minutesACID + failure-atomicity labNeo4j 2026.07.1 Community · Cypher 25Python driver 6.3.x optionalLast reviewed: September 2026

Learning outcomes

AtlasMart must reserve inventory and create a reservation record as one business state transition. If one half commits and the other fails, the graph is internally valid but the business state is wrong. Transactions are therefore not merely syntax around writes: they define what other work can observe, which locks remain held, and which failures are atomic.

01

Distinguish auto-commit, managed and explicit transactions in the current Python driver.

02

Explain Neo4j ACID guarantees together with the default read-committed isolation boundary.

03

Reason about statement boundaries, commit, rollback and failure atomicity.

04

Observe active transactions without confusing client timeouts with server commit outcomes.

05

Build a small AtlasMart reservation transaction whose post-commit invariants are testable.

Chapter 11 baseline · reviewed 9 September 2026

The mandatory lab continues Neo4j Community 2026.07.1, database neo4j, explicit CYPHER 25 for version-sensitive examples, container atlasmart-neo4j, authentication enabled, loopback Bolt/HTTP endpoints, no mandatory APOC/GDS plugin, and AtlasMart domain identifiers established in Chapters 01–10. Neo4j 5.26.30 remains the LTS comparison line. Optional application examples use the official Python driver 6.3.x.

Concurrency evidence note

This generation environment does not run Docker/Neo4j, so no deadlock frequency, wait duration, retry count, latency, lock list or cluster routing output is invented. The labs define deterministic invariants and controlled concurrency procedures that you execute on the disposable local instance. Community can use SHOW TRANSACTIONS for its own work; dbms.listActiveLocks() is currently Enterprise-only and is therefore optional evidence, not a mandatory lab dependency.

1. ACID is a transaction contract, not a promise that every application invariant is automatic

Property Neo4j transaction meaning Application responsibility
Atomicity All work in a transaction commits or the transaction is rolled back Put operations that must succeed/fail together in the same transaction
Consistency Committed database state respects database-level integrity rules Define business invariants with constraints plus transaction logic/tests
Isolation Default is read committed; uncommitted writes are not visible to other transactions Account for non-repeatable reads and explicit locking needs
Durability Committed work is recoverable through the transactional storage/logging mechanism Still design backup/recovery and ambiguous-client-outcome reconciliation

Read committed is deliberately weaker than serializable isolation. A transaction can read data, another transaction can commit a change, and a later read in the first transaction can observe a different committed value. Traversal results are not frozen simply because the transaction remains open.

2. Three client transaction styles solve different problems

Style Python driver shape Retry behavior Use when
Auto-commit session.run() No managed-transaction callback retry contract One Cypher statement is the whole unit of work
Managed session.execute_write(fn) / execute_read(fn) Driver may invoke the callback more than once for retryable failures A bounded unit of database work must retry safely
Explicit/unmanaged session.begin_transaction() Application controls begin/commit/rollback You need explicit transaction lifetime/control and accept responsibility for retry policy

A single Cypher statement is itself transactional. The reason to group multiple statements is business atomicity—not because individual Cypher statements otherwise bypass transactions.

Python · managed reservation unit of work
from neo4j import GraphDatabaseURI = "bolt://127.0.0.1:7687"AUTH = ("neo4j", "atlasmart-course-2026")def reserve(tx, reservation_id, qty):    record = tx.run("""        MATCH (s:Stock {stockId:'CH11-ST-001'})        WHERE s.onHand - s.reserved >= $qty        MERGE (r:Reservation {reservationId:$reservationId})        ON CREATE SET r.quantity=$qty, r.labTag='ch11'        MERGE (r)-[:RESERVES]->(s)        SET s.reserved = s.reserved + CASE WHEN r.quantity=$qty THEN $qty ELSE 0 END,            s.version = s.version + 1        RETURN s.onHand AS onHand,s.reserved AS reserved    """, reservationId=reservation_id, qty=qty).single()    if record is None:        raise ValueError("insufficient stock or reservation conflict")    return dict(record)with GraphDatabase.driver(URI, auth=AUTH) as driver:    with driver.session(database="neo4j") as session:        print(session.execute_write(reserve, "CH11-R-001", 3))

3. Failure atomicity is easiest to prove with a deliberate failure

Create the disposable fixture, begin an explicit transaction, update the stock, then execute a statement that violates the reservation uniqueness constraint. Roll the transaction back and verify that neither the stock counter nor reservation relationship changed. This demonstrates atomicity inside the database; it does not say anything about an email, payment call or message already sent outside Neo4j.

Cypher · Chapter 11 fixture
CYPHER 25CREATE CONSTRAINT ch11_stock_id IF NOT EXISTSFOR (s:Stock) REQUIRE s.stockId IS UNIQUE;CREATE CONSTRAINT ch11_reservation_id IF NOT EXISTSFOR (r:Reservation) REQUIRE r.reservationId IS UNIQUE;MERGE (p:Product {productId:'CH11-P-001'})SET p.name='AtlasCam Pro', p.labTag='ch11'MERGE (store:Store {storeId:'CH11-S-001'})SET store.name='Central Store', store.labTag='ch11'MERGE (stock:Stock {stockId:'CH11-ST-001'})SET stock.onHand=20, stock.reserved=0, stock.version=0, stock.labTag='ch11'MERGE (store)-[:HOLDS_STOCK]->(stock)-[:FOR_PRODUCT]->(p);MERGE (a:Counter {counterId:'CH11-A'}) SET a.value=0,a.labTag='ch11';MERGE (b:Counter {counterId:'CH11-B'}) SET b.value=0,b.labTag='ch11';
Cypher · verify committed business state
CYPHER 25MATCH (s:Stock {stockId:'CH11-ST-001'})OPTIONAL MATCH (s)<-[:RESERVES]-(r:Reservation {labTag:'ch11'})RETURN s.onHand AS onHand,       s.reserved AS reserved,       coalesce(sum(r.quantity),0) AS reservationQuantity,       count(r) AS reservationCount,       s.reserved <= s.onHand AS capacityInvariant,       s.reserved = coalesce(sum(r.quantity),0) AS reconciliationInvariant;

4. Observe transaction lifetime

SHOW TRANSACTIONS exposes running work, including query identifiers and timing fields available to the current user/privilege set. Keep a transaction open only long enough to reproduce the signal; do not introduce arbitrary user/network waits into a production write transaction.

Cypher · inspect active work
CYPHER 25SHOW TRANSACTIONSYIELD transactionId,currentQueryId,currentQuery,status,elapsedTime,waitTime,idleTimeRETURN transactionId,currentQueryId,status,elapsedTime,waitTime,idleTime,currentQueryORDER BY elapsedTime DESC;

5. Deliberately wrong: treat a client timeout as proof that the transaction rolled back

A transport timeout or dropped connection can leave the application uncertain whether the server committed. Retrying a non-idempotent operation blindly can therefore duplicate effects. The repair is a stable operation/reservation identifier plus reconciliation: query by the operation ID before deciding whether to retry, compensate or surface an ambiguous outcome.

Check your understanding

  1. What is Neo4j’s default isolation level?
  2. Does opening an explicit transaction make reads repeatable by itself?
  3. Why use a managed transaction callback?
  4. What does a client timeout prove about commit?
  5. What makes a reservation workflow auditable?
Review the answers

1. Read committed.

2. No. Read committed still allows non-repeatable reads unless stronger locking is deliberately introduced.

3. It groups database work into a unit the driver can retry for retryable failures, provided the callback is safe to invoke more than once.

4. Only that the client did not receive a conclusive result; it does not necessarily prove commit or rollback.

5. Stable operation identity, atomic graph changes, post-commit invariants and reconciliation for ambiguous outcomes.

Summary and next step

Transactions define atomic state transitions, but concurrency determines how those transitions interact. Next we inspect locks, contention hotspots and deadlock detection.

Authoritative references

Keep knowledge open

Help the academy stay free and grow.

If these tutorials save you time, a small donation supports new lessons, technical review, diagrams, examples, and long-term maintenance.

ETHEthereum / ERC-20 only
0x716c4Ab160C4B66F31a28AE2448BfF68fc3a2ef0

Send only Ethereum or ERC-20 compatible assets to this address.