Locks and deadlocks
Neo4j is fully ACID compliant. This means that all database operations which access graphs, indexes, or schemas must be performed in a transaction. When a write transaction occurs, Neo4j takes locks to preserve data consistency while updating.
Locks
Locks are taken automatically by the queries that users run. They ensure that a node/relationship is locked to one particular transaction until that transaction is completed. In other words, a lock on a node or a relationship by one transaction will pause additional transactions which seek to concurrently modify the same node or relationship. As such, locks prevent concurrent modifications of shared resources between transactions.
Locks are used in Neo4j to ensure data consistency and isolation levels. They not only protect logical entities (such as nodes and relationships), but also the integrity of internal data structures.
The default isolation is read-committed isolation level. It is, however, possible to manually acquire write locks on nodes and relationships. For more information on how to manually acquire write locks, see Neo4j Java Reference Manual → Transaction management.
Lock contention
Lock contention may arise if an application needs to perform concurrent updates on the same nodes/relationships. In such a scenario, transactions must wait for locks held by other transactions to be released in order to be completed. If two or more transactions attempt to modify the same data concurrently, it will increase the likelihood of a deadlock (explained in more detail below). In larger graphs, it is less likely that two transactions modify the same data concurrently, and so the likelihood of a deadlock is reduced. That said, even in large graphs, a deadlock can occur if two or more transactions are attempting to modify the same data concurrently.
Locks in practice
Modification | Lock taken |
---|---|
Creating a node |
No lock |
Updating a node label |
Node lock |
Updating a node property |
Node lock |
Deleting a node |
Node lock |
Creating a relationship* |
If node is sparse: node lock. If node is dense: node delete prevention lock.** |
Updating a relationship property |
Relationship lock |
Deleting a relationship* |
If node is sparse: node lock. If node is dense: node delete prevention lock. Relationship lock for both sparse and dense nodes. |
*Applies for both source nodes and target nodes.
**A node is considered dense if it at any point has had 50 or more relationships (i.e. it will still be considered dense even it comes to have less than 50 relationships at any point in the future). A node is considered sparse if it has never had more than 50 relationships.
Additional locks are often taken to maintain indexes and other internal structures, depending on how other data in the graph is affected by a transaction. For these additional locks, no assumptions or guarantees can be made with regard to which lock will or will not be taken.
Locks and dense nodes
When creating or deleting relationships in Neo4j, dense nodes are not exclusively locked during a transaction (a node is considered dense if, at any point in time, it has had more than 50 relationships). Rather, internally shared locks prevent the deletion of nodes, and shared degree locks are acquired for synchronizing with concurrent label changes for those nodes (to ensure correct count updates).
At commit time, relationships are inserted into their relationship chains at places that are currently uncontested (i.e. not currently modified by another transaction), and the surrounding relationships are exclusively locked.
In other words, relationship modifications acquires coarse-grained shared node locks when doing the operation in the transaction, and then acquires precise exclusive relationship locks during commit.
The locking is very similar for sparse and dense nodes. The biggest contention for sparse nodes is the update of the degree (i.e. number of relationships) for the node. Dense nodes store this data in a concurrent data structure, and so can avoid exclusive node locks in almost all cases for relationship modifications.
Deadlocks
A deadlock occurs when two transactions are blocked by each other because they are attempting to concurrently modify a node or a relationship that is locked by the other transaction. In such a scenario, neither of the transactions will be able to proceed.
When Neo4j detects a deadlock, the transaction is terminated (with the transient error message code Neo.TransientError.Transaction.DeadlockDetected
).
For example, running the following two queries in Cypher-shell at the same time, will result in a deadlock because they are attempting to modify the same node properties concurrently:
:begin
MATCH (n:Test) SET n.prop = 1
WITH collect(n) as nodes
CALL apoc.util.sleep(5000)
MATCH (m:Test2) SET m.prop = 1;
:begin
MATCH (n:Test2) SET n.prop = 1
WITH collect(n) as nodes
CALL apoc.util.sleep(5000)
MATCH (m:Test) SET m.prop = 1;
The following error message is thrown:
The transaction will be rolled back and terminated. Error: ForsetiClient[transactionId=6698, clientId=1] can't acquire ExclusiveLock{owner=ForsetiClient[transactionId=6697, clientId=3]} on NODE(27), because holders of that lock are waiting for ForsetiClient[transactionId=6698, clientId=1].
Wait list:ExclusiveLock[
Client[6697] waits for [ForsetiClient[transactionId=6698, clientId=1]]]
The Cypher clause |
Avoiding deadlocks
Most likely, a deadlock will be resolved by retrying the transaction. This will, however, negatively impact the total transactional throughput of the database, so it is useful to know about strategies to avoid deadlocks.
Neo4j assists transactions by internally sorting operations (see below for more information about internal locks).
However, this internal sorting only applies for the locks taken when creating or deleting relationships.
Users are, therefore, encouraged to sort their operations in cases where Neo4j does not internally assist, such as when locks are taken for property updates.
This is done by ensuring that updates occur in the same order.
For example, if the three locks A
, B
, and C
are always taken in the same order (e.g. A→B→C
), then a transaction will never hold lock B
while waiting for lock A
to be released, and so a deadlock will not occur.
Another option is to avoid lock contention by not modifying the same entities concurrently.
For more information about deadlocks, see Neo4j Java Reference Manual → Transaction management.
Internal lock types
To avoid deadlocks, internal locks should be taken in the following order:
Lock type | Locked entity | Description |
---|---|---|
|
Token id |
Schema locks, which lock indexes and constraints on the particular label or relationship type. |
|
Schema name |
Lock a schema name to avoid duplicates. Note, collisions are possible because the hash is stringed (this only affects concurrency and not correctness). |
|
Node id |
Lock taken on a node during the transaction creation phase to prevent deletion of said node and/or relationship group.
This is different from the |
|
Node id |
Lock on a node, used to prevent concurrent updates to the node records (i.e. add/remove label, set property, add/remove relationship). Note that updating relationships will only require a lock on the node if the head of the relationship chain/relationship group chain must be updated, since that is the only data part of the node record. |
|
Node id |
Used to lock nodes to avoid concurrent label changes when a relationship is added or deleted. Such an update would otherwise lead to an inconsistent count store. |
|
Relationship id |
Lock a relationship for exclusive access during deletion. |
|
Node id |
Lock the full relationship group chain for a given dense node.*
This will not lock the node, in contrast to the lock |
|
Relationship |
Lock on a relationship, or more specifically a relationship record, to prevent concurrent updates. |
*A node is considered dense if it at any point has had 50 or more relationships (i.e. it will still be considered dense even it comes to have less than 50 relationships at any point in the future).
Note that these lock types may change without any notification between different Neo4j versions.
Was this page helpful?