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

Table 1. Locks taken for specific graph modifications
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:

Transaction A
: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;
Transaction B
: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 MERGE takes locks out of order to ensure uniqueness of the data, and this may prevent Neo4j’s internal sorting operations from ordering transactions in a way which avoids deadlocks. When possible, users are, therefore, encouraged to use the Cypher clause CREATE instead, which does not take locks out of order.

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

LABEL or RELATIONSHIP_TYPE

Token id

Schema locks, which lock indexes and constraints on the particular label or relationship type.

SCHEMA_NAME

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_RELATIONSHIP_GROUP_DELETE

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 lock to allow concurrent label and property changes together with relationship modifications.

NODE

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.

DEGREES

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_DELETE

Relationship id

Lock a relationship for exclusive access during deletion.

RELATIONSHIP_GROUP

Node id

Lock the full relationship group chain for a given dense node.* This will not lock the node, in contrast to the lock NODE_RELATIONSHIP_GROUP_DELETE.

RELATIONSHIP

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.