Chapter 11 · Transactions, Isolation, Locking, Deadlocks, Retries, and Consistency
Locks on Nodes/Relationships/Schema, Contention Patterns, Dense Graph Hotspots, and Deadlock Detection
Diagnose AtlasMart contention and deadlocks from resource overlap, lock lifetime and transaction order rather than from generic “database slowness.”
Learning outcomes
Two reservations can be individually correct yet contend on the same stock entity. Neo4j locks graph entities and internal structures to preserve consistency; those locks can serialize hot writes or form a deadlock cycle when transactions acquire overlapping resources in incompatible order.
Explain default node/relationship locking at the level applications can safely rely on.
Distinguish lock contention from deadlock and identify the business shapes that create hotspots.
Understand automatic dependent-property locking and the lost-update boundary.
Reproduce a reversible two-resource deadlock without APOC or production data.
Use Community-safe transaction evidence and label Enterprise-only active-lock introspection correctly.
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.
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. Locks exist to serialize conflicting modifications
Updating a node property takes a node write lock; updating a relationship property takes a relationship lock. Relationship creation/deletion also protects its endpoints according to the current storage representation. Additional internal locks may be taken to maintain indexes and graph structures; application code should not depend on undocumented internal lock ordering.
| Workload shape | Likely symptom | Design question |
|---|---|---|
| Many writers update one Stock node | Wait time / throughput plateau | Can inventory be partitioned by store/SKU/bucket while preserving invariants? |
| Transactions touch A then B and others B then A | Deadlock risk | Can every workflow acquire resources in a deterministic order? |
| Long transaction holds changed entities | Long waits and memory retention | Can work be shortened or external waits moved outside the transaction? |
| Dense relationship churn | High concurrent structural work | Are relationship semantics/ownership appropriately partitioned? |
2. Read committed still needs lost-update reasoning
Cypher automatically acquires a write lock before reading a
property when the right side of a SET directly
depends on that property, such as
SET s.reserved = s.reserved + $qty. More complex
workflows can read a value into an earlier variable and later
write a derived value without such a direct dependency. Current
Neo4j documentation shows a dummy-property write/remove as a
workaround for explicitly taking a node write lock before the
dependent read.
CYPHER 25MATCH (s:Stock {stockId:'CH11-ST-001'})SET s._ch11_lock = trueREMOVE s._ch11_lockWITH s, s.onHand - s.reserved AS availableWITH s, available, CASE WHEN available >= $qty THEN $qty ELSE 0 END AS acceptedSET s.reserved = s.reserved + accepted, s.version = s.version + CASE WHEN accepted > 0 THEN 1 ELSE 0 ENDRETURN available,accepted,s.reserved,s.version;
3. Contention waits; deadlock cycles
Contention means one transaction waits for a lock and can
proceed when the holder finishes. A deadlock means transactions
form a wait cycle and none can progress. Neo4j detects the cycle
and aborts one transaction with
Neo.TransientError.Transaction.DeadlockDetected;
current releases also report GQLSTATUS 50N05.
import threading, timefrom neo4j import GraphDatabaseURI='bolt://127.0.0.1:7687'AUTH=('neo4j','atlasmart-course-2026')barrier = threading.Barrier(2)def worker(first, second): with driver.session(database='neo4j') as session: tx = session.begin_transaction(timeout=10) try: tx.run("MATCH (n:Counter {counterId:$id}) SET n.value=n.value+1", id=first).consume() barrier.wait() time.sleep(0.2) tx.run("MATCH (n:Counter {counterId:$id}) SET n.value=n.value+1", id=second).consume() tx.commit() print(first, 'committed') except Exception as exc: print(first, type(exc).__name__, exc) tx.rollback()with GraphDatabase.driver(URI, auth=AUTH) as driver: t1=threading.Thread(target=worker,args=('CH11-A','CH11-B')) t2=threading.Thread(target=worker,args=('CH11-B','CH11-A')) t1.start(); t2.start(); t1.join(); t2.join()
Run only against the disposable Chapter 11 counters. Exact timing can vary; the invariant is the wait-cycle shape, not that every run must deadlock at precisely the same millisecond.
4. Observe blocked work without requiring Enterprise
CYPHER 25SHOW TRANSACTIONSYIELD transactionId,currentQueryId,currentQuery,status,elapsedTime,waitTimeWHERE currentQuery CONTAINS 'CH11-'RETURN transactionId,currentQueryId,status,elapsedTime,waitTime,currentQuery;
On Enterprise, an administrator can additionally call
dbms.listActiveLocks(currentQueryId). That
procedure is Enterprise-only in the current Operations Manual,
so the mandatory Community lab uses transaction state,
controlled timing, driver exceptions and invariant checks
instead.
5. Repair: deterministic acquisition order and shorter transactions
A common deadlock reduction strategy is to sort business resource identifiers and modify them in the same order in every transaction. This reduces lock-order cycles without changing isolation. It does not eliminate contention when all requests still target the same hot entity.
Check your understanding
- What is the difference between contention and deadlock?
- Which error identifies a detected Neo4j deadlock?
- Does every property read automatically lock the entity for the rest of the transaction?
- Is dbms.listActiveLocks a mandatory Community diagnostic?
- Why sort resources before updating several of them?
Review the answers
1. Contention is waiting that can resolve when a lock is released; deadlock is a cycle of waits that cannot resolve without aborting a participant.
2. Neo.TransientError.Transaction.DeadlockDetected; current releases also include GQLSTATUS 50N05.
3. No. Read committed does not make ordinary reads repeatable; automatic write-lock acquisition depends on the write dependency pattern.
4. No. It is currently Enterprise-only.
5. A consistent acquisition order reduces wait cycles and therefore deadlock risk.
Summary and next step
Neo4j detects deadlocks, but the application still needs a retry policy. Next we design managed-transaction retries so transient failure handling does not duplicate business or external effects.
Authoritative references
- Current Neo4j versions — Release/LTS snapshot used for this chapter.
- Database internals and transactional behavior — ACID behavior, read-committed isolation and transaction internals.
- Database transactions — Transaction lifecycle, memory and completion behavior.
- Concurrent data access — Locks, lost updates, contention, deadlocks and lock timeout semantics.
- Show and terminate transactions — SHOW TRANSACTIONS visibility and transaction diagnostics.
- Neo4j Python Driver 6.3 API — Current official Python driver, Bolt compatibility and transaction APIs.
- Python driver managed transactions — Managed transaction functions, retries and explicit transaction patterns.
- Python driver bookmarks — Bookmark propagation and causal chaining across sessions.