CALL subqueries in transactions
CALL
subqueries can be made to execute in separate, inner transactions, producing intermediate commits.
This can be useful when doing large write operations, like batch updates, imports, and deletes.
To execute a CALL
subquery in separate transactions, add the modifier IN TRANSACTIONS
after the subquery.
An outer transaction is opened to report back the accumulated statistics for the inner transactions
(created and deleted nodes, relationships, etc.) and it will succeed or fail depending on the results
of those inner transactions.
By default, inner transactions group together batches of 1000 rows.
Cancelling the outer transaction will cancel the inner ones as well.
CALL { … } IN TRANSACTIONS is only allowed in implicit transactions.If you are using Neo4j Browser, you must prepend any queries using CALL { … } IN TRANSACTIONS with :auto .
|
The examples on this page use a variable scope clause to import variables into the CALL subquery.
|
Syntax
CALL {
subQuery
} IN [[concurrency] CONCURRENT] TRANSACTIONS
[OF batchSize ROW[S]]
[REPORT STATUS AS statusVar]
[ON ERROR {CONTINUE | BREAK | FAIL | RETRY [FOR] [duration SEC[OND[S]]] [THEN {CONTINUE | BREAK | FAIL}]}]
Loading CSV data
This example uses a CSV file and the LOAD CSV
clause to import data into the database.
It creates nodes in separate transactions using CALL { … } IN TRANSACTIONS
:
1,Bill,26
2,Max,27
3,Anna,22
4,Gladys,29
5,Summer,24
LOAD CSV FROM 'file:///friends.csv' AS line
CALL (line) {
CREATE (:Person {name: line[1], age: toInteger(line[2])})
} IN TRANSACTIONS
|
Rows: 0 |
As the size of the CSV file in this example is small, only a single separate transaction is started and committed.
Deleting a large volume of data
Using CALL { … } IN TRANSACTIONS
is the recommended way of deleting a large volume of data.
MATCH (n)
CALL (n) {
DETACH DELETE n
} IN TRANSACTIONS
|
Rows: 0 |
The CALL { … } IN TRANSACTIONS
subquery should not be modified.
Any necessary filtering can be done before the subquery.
MATCH (n:Label) WHERE n.prop > 100
CALL (n) {
DETACH DELETE n
} IN TRANSACTIONS
|
Rows: 0 |
The batching is performed on the input rows fed into |
Batching
The amount of work to do in each separate transaction can be specified in terms of how many input rows
to process before committing the current transaction and starting a new one.
The number of input rows is set with the modifier OF n ROWS
(or OF n ROW
).
If omitted, the default batch size is 1000
rows.
The number of rows can be expressed using any expression that evaluates to a positive integer and does not refer to nodes or relationships.
This example loads a CSV file with one transaction for every 2
input rows:
1,Bill,26
2,Max,27
3,Anna,22
4,Gladys,29
5,Summer,24
LOAD CSV FROM 'file:///friends.csv' AS line
CALL (line) {
CREATE (:Person {name: line[1], age: toInteger(line[2])})
} IN TRANSACTIONS OF 2 ROWS
|
Rows: 0 |
The query now starts and commits three separate transactions:
-
The first two executions of the subquery (for the first two input rows from
LOAD CSV
) take place in the first transaction. -
The first transaction is then committed before proceeding.
-
The next two executions of the subquery (for the next two input rows) take place in a second transaction.
-
The second transaction is committed.
-
The last execution of the subquery (for the last input row) takes place in a third transaction.
-
The third transaction is committed.
You can also use CALL { … } IN TRANSACTIONS OF n ROWS
to delete all your data in batches in order to avoid a huge garbage collection or an OutOfMemory
exception.
For example:
MATCH (n)
CALL (n) {
DETACH DELETE n
} IN TRANSACTIONS OF 2 ROWS
|
Rows: 0 |
Up to a point, using a larger batch size will be more performant.
The batch size of |
Composite databases
CALL { … } IN TRANSACTIONS
can be used with composite databases.
Even though composite databases allow accessing multiple graphs in a single query, only one graph can be modified in a single transaction.
CALL { … } IN TRANSACTIONS
offers a way of constructing queries which modify multiple graphs.
While the previous examples are generally valid for composite databases, there’s a few extra factors that come into play when working with composite databases in subqueries.
The following examples show how you can use CALL { … } IN TRANSACTIONS
on a composite database.
1,Bill,26
2,Max,27
3,Anna,22
4,Gladys,29
5,Summer,24
Person
nodes on all constituents, drawing data from friends.csvUNWIND graph.names() AS graphName
LOAD CSV FROM 'file:///friends.csv' AS line
CALL (*) {
USE graph.byName( graphName )
CREATE (:Person {name: line[1], age: toInteger(line[2])})
} IN TRANSACTIONS
UNWIND graph.names() AS graphName
CALL {
USE graph.byName( graphName )
MATCH (n)
RETURN elementId(n) AS id
}
CALL {
USE graph.byName( graphName )
WITH id
MATCH (n)
WHERE elementId(n) = id
DETACH DELETE n
} IN TRANSACTIONS
Since the batching is performed on the input rows fed into CALL { … } IN TRANSACTIONS , the data must be supplied from outside the subquery in order for the batching to have an effect.
That is why the nodes are matched in a subquery preceding the one that actually deletes the data.
If the MATCH clause were inside the second subquery, the data deletion would run as one single transaction.
|
There is currently a known issue.
When an error occurs during |
Batch size in composite databases
Because CALL { … } IN TRANSACTIONS
subqueries targeting different graphs can’t be interleaved, if a USE
clause evaluates to a different target than the current one, the current batch is committed and the next batch is created.
The batch size declared with IN TRANSACTIONS OF … ROWS
represents an upper limit of the batch size, but the real batch size depends on how many input rows target one database in sequence.
Every time the target database changes, the batch is committed.
IN TRANSACTIONS OF ROWS
on composite databasesThe next example assumes the existence of two constituents remoteGraph1
and remoteGraph2
for the composite database composite
.
While the declared batch size is 3, only the first 2 rows act on composite.remoteGraph1
, so the batch size for the first transaction is 2. That is followed by 3 rows on composite.remoteGraph2
, 1 on composite.remoteGraph2
and finally 2 on composite.remoteGraph1
.
WITH ['composite.remoteGraph1', 'composite.remoteGraph2'] AS graphs
UNWIND [0, 0, 1, 1, 1, 1, 0, 0] AS i
WITH graphs[i] AS g
CALL (g) {
USE graph.byName( g )
CREATE ()
} IN TRANSACTIONS OF 3 ROWS
Error behavior
CALL { … } IN TRANSACTIONS
has four different behavioral options in case an error occurs in any of the inner transactions: ON ERROR CONTINUE
, ON ERROR BREAK
, ON ERROR FAIL
, and ON ERROR RETRY
.
If an error occurs, any inner transactions that were successfully committed remain unchanged and are not rolled back. However, any inner transactions that failed are fully rolled back. This behavior applies regardless of which ON ERROR option is used.
|
ON ERROR CONTINUE
ON ERROR CONTINUE
ignores recoverable errors and continues the execution of subsequent inner transactions.
The outer transaction succeeds.
When an inner query fails, ON ERROR CONTINUE
ensures the outer transaction continues, returning null
for the failed inner query.
ON ERROR CONTINUE
In the below query, the last subquery execution in the second inner transaction fails due to division by zero:
UNWIND [4, 2, 1, 0] AS i
CALL (i) {
CREATE (:Person {num: 100/i}) // Note, fails when i = 0
} IN TRANSACTIONS OF 2 ROWS
RETURN i
/ by zero (Transactions committed: 1)
Since the failure occurred after the first transaction was committed, the database retains the successfully created nodes.
MATCH (e:Person)
RETURN e.num
e.num |
---|
|
|
Rows: 2 |
In the following example, ON ERROR CONTINUE
is used after a failed inner transaction to execute the remaining inner transactions and not fail the outer transaction:
ON ERROR CONTINUE
UNWIND [1, 0, 2, 4] AS i
CALL (i) {
CREATE (n:Person {num: 100/i}) // Note, fails when i = 0
RETURN n
} IN TRANSACTIONS
OF 1 ROW
ON ERROR CONTINUE
RETURN n.num
n.num |
---|
|
|
|
|
Rows: 4 |
Note the difference in results when batching in transactions of 2 rows:
ON ERROR CONTINUE
UNWIND [1, 0, 2, 4] AS i
CALL (i) {
CREATE (n:Person {num: 100/i})
RETURN n
} IN TRANSACTIONS
OF 2 ROWS
ON ERROR CONTINUE
RETURN n.num
n.num |
---|
|
|
|
|
Rows: 4 |
In this case, the first inner transaction includes i = 1
and i = 0
.
Since i = 0
causes an error, the entire transaction is rolled back, resulting in null
for both elements.
ON ERROR BREAK
ON ERROR BREAK
ignores recoverable errors and stops the execution of subsequent inner transactions.
The outer transaction succeeds.
When an inner query fails, ON ERROR BREAK
ensures the outer transaction continues but stops executing any further inner transactions.
Expected variables from the failed inner query are bound as null
for all subsequent transactions, including the failed one.
ON ERROR BREAK
In this example, ON ERROR BREAK
ensures that once an inner transaction fails (i = 0)
, no further inner transactions execute, while the outer transaction remains successful.
ON ERROR BREAK
UNWIND [1, 0, 2, 4] AS i
CALL (i) {
CREATE (n:Person {num: 100/i}) // Note, fails when i = 0
RETURN n
} IN TRANSACTIONS
OF 1 ROW
ON ERROR BREAK
RETURN n.num
n.num |
---|
|
|
|
|
Rows: 4 |
When batching in transactions of 2 rows, the first transaction (i = 1, 0
) encounters an error at i = 0
, causing execution to stop immediately.
As a result, all remaining transactions also return null.
ON ERROR BREAK
UNWIND [1, 0, 2, 4] AS i
CALL (i) {
CREATE (n:Person {num: 100/i})
RETURN n
} IN TRANSACTIONS
OF 2 ROWS
ON ERROR BREAK
RETURN n.num
n.num |
---|
|
|
|
|
Rows: 4 |
ON ERROR FAIL
ON ERROR FAIL
acknowledges a recoverable error and stops the execution of subsequent inner transactions, causing the outer transaction to fail.
ON ERROR FAIL is the default behavior if no error-handling flag is explicitly specified.
|
ON ERROR FAIL
In the following example, ON ERROR FAIL
is used after the failed inner transaction to prevent the remaining inner transactions from executing and to cause the outer transaction to fail as well.
ON ERROR FAIL
UNWIND [1, 0, 2, 4] AS i
CALL (i) {
CREATE (n:Person {num: 100/i}) // Note, fails when i = 0
RETURN n
} IN TRANSACTIONS
OF 1 ROW
ON ERROR FAIL
RETURN n.num
/ by zero (Transactions committed: 1)
ON ERROR RETRY
ON ERROR RETRY
uses an exponential delay between retry attempts for transaction batches that fail due to transient errors (i.e. errors where retrying a transaction can be expected to give a different result), with an optional maximum retry duration.
If the transaction still fails after the maximum duration, the failure is handled according to an optionally specified fallback error handling mode (THEN CONTINUE
, THEN BREAK
, THEN FAIL
(default)).
ON ERROR RETRY
increases query robustness by handling transient errors without manual intervention.
It is particularly suitable for concurrent transactions, reducing the likelihood of a query failing due to deadlocks.
Queries using ON ERROR RETRY can currently only use the slotted runtime.
|
The below example demonstrates a basic retry scenario.
If a transient error occurs during the creation of a User
node, the transaction will be retried for the default maximum retry duration (30
seconds).
If the retry succeeds, the query continues.
If the retry fails after the default duration, the query fails because it behaves like ON ERROR FAIL
(the default fallback).
ON ERROR RETRY
UNWIND range(1, 100) AS i
CALL (i) {
MERGE (u:User {id: i})
ON CREATE SET u.created = timestamp()
} IN TRANSACTIONS ON ERROR RETRY
Specify a maximum retry duration
The default maximum retry duration is 30
seconds.
A new default can be set using dbms.cypher.transactions.default_subquery_retry_timeout
.
A maximum retry duration can be specified for individual queries as follows: ON ERROR RETRY [FOR] <duration> SEC[OND[S]]
, where <duration> is an INTEGER
or FLOAT
value representing seconds that is greater than or equal to 0
.
Decimal values are allowed, and the <duration>
can be set with a parameter.
Note that this <duration>
overrides the default value.
The duration timer starts when the first retry is scheduled.
As a result, regardless of the specified duration, a minimum of one retry will be attempted.
If the transaction still fails with a transient error, a new attempt with made unless the duration has expired.
For example, a value of 0
(or close to 0
) will result in a retry, but guarantees that only a single retry will be attempted.
In this example, the retry duration is explicitly set to 2.5
seconds.
This means that the transaction will be retried until it succeeds or until 2.5
seconds have elapsed.
ON ERROR RETRY
with a set durationUNWIND range(1, 100) AS i
CALL (i) {
MERGE (u:User {id: i})
ON CREATE SET u.created = timestamp()
} IN TRANSACTIONS ON ERROR RETRY FOR 2.5 SECONDS
{
"duration": 10
}
ON ERROR RETRY
using a parameter for the durationUNWIND range(1, 100) AS i
CALL (i) {
MERGE (u:User {id: i})
ON CREATE SET u.created = timestamp()
} IN TRANSACTIONS ON ERROR RETRY FOR $duration SECONDS
Fallback error handling options
ON ERROR RETRY
can be combined with the other ON ERROR
options via the THEN
clause to specify a fallback behavior.
The fallback behavior specifies what will happen if a transaction has not succeeded within the time limit.
Specifically:
-
ON ERROR RETRY … THEN CONTINUE
: the query will ignore recoverable errors and continue with the execution of subsequent inner transactions. The outer transaction succeeds, andnull
will be returned for any failed inner transactions. SeeON ERROR CONTINUE
for more information about this behavior. -
ON ERROR RETRY … THEN BREAK
: the query will ignore recoverable errors and stop the execution of subsequent inner transactions. The outer transaction succeeds, andnull
will be returned for the failed inner transaction and all subsequent ones. SeeON ERROR BREAK
for more information about this behavior. -
ON ERROR RETRY … THEN FAIL
(default): the query will acknowledge a recoverable error and stop the execution of subsequent inner transactions, causing the outer transaction to fail. SeeON ERROR FAIL
for more information about this behavior.
Because THEN FAIL is the default fallback option it does not have to be specified.
|
UNWIND range(1, 100) AS i
CALL (i) {
MERGE (u:User {id: i})
ON CREATE SET u.created = timestamp()
} IN TRANSACTIONS ON ERROR RETRY FOR 1 SECOND THEN CONTINUE
UNWIND range(1, 100) AS i
CALL (i) {
MERGE (u:User {id: i})
ON CREATE SET u.created = timestamp()
} IN TRANSACTIONS ON ERROR RETRY FOR 1 SECOND THEN BREAK
UNWIND range(1, 100) AS i
CALL (i) {
MERGE (u:User {id: i})
ON CREATE SET u.created = timestamp()
} IN TRANSACTIONS ON ERROR RETRY FOR 1 SECOND THEN FAIL
Status report
Users can also report the execution status of the inner transactions by using REPORT STATUS AS var
.
This flag is disallowed for ON ERROR FAIL
. For more information, see Error behavior.
After each execution of the inner query finishes (successfully or not), a status value is created that records information about the execution and the transaction that executed it:
-
If the inner execution produces one or more rows as output, then a binding to this status value is added to each row, under the selected variable name.
-
If the inner execution fails then a single row is produced containing a binding to this status value under the selected variable, and null bindings for all variables that should have been returned by the inner query (if any).
The status value is a map value with the following fields:
-
started
:true
when the inner transaction was started,false
otherwise. -
committed
,true
when the inner transaction changes were successfully committed,false
otherwise. -
transactionId
: the inner transaction id, ornull
if the transaction was not started. -
errorMessage
, the inner transaction error message, ornull
in case of no error.
Example of reporting status with ON ERROR CONTINUE
:
UNWIND [1, 0, 2, 4] AS i
CALL (i) {
CREATE (n:Person {num: 100/i}) // Note, fails when i = 0
RETURN n
} IN TRANSACTIONS
OF 1 ROW
ON ERROR CONTINUE
REPORT STATUS AS s
RETURN n.num, s
n.num | s |
---|---|
|
|
|
|
|
|
|
|
Rows: 4 |
Example of reporting status with ON ERROR BREAK
:
UNWIND [1, 0, 2, 4] AS i
CALL (i) {
CREATE (n:Person {num: 100/i}) // Note, fails when i = 0
RETURN n
} IN TRANSACTIONS
OF 1 ROW
ON ERROR BREAK
REPORT STATUS AS s
RETURN n.num, s.started, s.committed, s.errorMessage
n.num | s.started | s.committed | s.errorMessage |
---|---|---|---|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
Rows: 4 |
Reporting status with ON ERROR FAIL
is disallowed:
UNWIND [1, 0, 2, 4] AS i
CALL (i) {
CREATE (n:Person {num: 100/i}) // Note, fails when i = 0
RETURN n
} IN TRANSACTIONS
OF 1 ROW
ON ERROR FAIL
REPORT STATUS AS s
RETURN n.num, s.errorMessage
REPORT STATUS can only be used when specifying ON ERROR CONTINUE or ON ERROR BREAK
Concurrent transactions
By default, CALL { … } IN TRANSACTIONS
is single-threaded; one CPU core is used to sequentially execute batches.
However, CALL
subqueries can also execute batches in parallel by appending IN [n] CONCURRENT TRANSACTIONS
, where n
is a concurrency value used to set the maximum number of transactions that can be executed in parallel.
This allows CALL
subqueries to utilize multiple CPU cores simultaneously, which can significantly reduce the time required to execute a large, outer transaction.
The concurrency value is optional. If not specified, a default value based on the amount of available CPU cores will be chosen. If a negative number is specified (which can only be done through a parameter), the concurrency will be the number of available CPU cores reduced by the absolute value of that number. |
CALL { … } IN CONCURRENT TRANSACTIONS
is particularly suitable for importing data without dependencies.
This example creates Person
nodes from a unique tmdbId
value assigned to each person row in the CSV file (444 in total) in 3 concurrent transactions.
LOAD CSV WITH HEADERS FROM 'https://data.neo4j.com/importing-cypher/persons.csv' AS row
CALL (row) {
CREATE (p:Person {tmdbId: row.person_tmdbId})
SET p.name = row.name, p.born = row.born
} IN 3 CONCURRENT TRANSACTIONS OF 10 ROWS
RETURN count(*) AS personNodes
personNodes |
---|
|
Rows: 1 |
Concurrency and non-deterministic results
CALL { … } IN TRANSACTIONS
uses ordered semantics by default, where batches are committed in a sequential row-by-row order.
For example, in CALL { <I> } IN TRANSACTIONS
, any writes done in the execution of <I1>
must be observed by <I2>
, and so on.
In contrast, CALL { … } IN CONCURRENT TRANSACTIONS
uses concurrent semantics, where both the number of rows committed by a particular batch and the order of committed batches is undefined.
That is, in CALL { <I> } IN CONCURRENT TRANSACTIONS
, writes committed in the execution of <I1>
may or may not be observed by <I2>
, and so on.
The results of CALL
subqueries executed in concurrent transactions may, therefore, not be deterministic.
To guarantee deterministic results, ensure that the results of committed batches are not dependent on each other.
Using CALL { … } IN CONCURRENT TRANSACTIONS can impact error behavior.
Specifically, when using ON ERROR BREAK or ON ERROR FAIL and one transaction fails, then any concurrent transactions may not be interrupted and rolled back (though subsequent ones would).
This is because no timing guarantees can be given for concurrent transactions.
That is, an ongoing transaction may or may not commit successfully in the time window when the error is being handled.
Use the status report to determine which batches were committed and which failed or did not start.
|
Deadlocks
When a write transaction occurs, Neo4j takes locks to preserve data consistency while updating. For example, when creating or deleting a relationship, a write lock is taken on both the specific relationship and its connected nodes.
A deadlock happens 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 (for more information about locks and deadlocks in Neo4j, see Operations Manual → Concurrent data access.
A deadlock may occur when using CALL { … } IN CONCURRENT TRANSACTIONS
if the transactions for two or more batches try to take the same locks in an order that results in a circular dependency between them.
If so, the impacted transactions are always rolled back, and an error is thrown unless the query is appended with one of the following error options:
-
ON ERROR RETRY
Introduced in Neo4j 2025.03
The latter is particularly suited for concurrent transactions, because it retries recoverable transient errors with exponential backoff between retries until the maximum retry duration has been reached.
Deadlock detection and transaction retries can be time-consuming. When importing data that includes a significant number of relationships to be merged between the same nodes but processed in different batches, increasing the concurrency may not enhance performance. On the contrary, it could slow down the import process. |
The following query tries to create Movie
and Year
nodes connected by a RELEASED_IN
relationship.
Note that there are only three different years in the CSV file, meaning that only three Year
nodes should be created.
LOAD CSV WITH HEADERS FROM 'https://data.neo4j.com/importing-cypher/movies.csv' AS row
CALL (row) {
MERGE (m:Movie {movieId: row.movieId})
MERGE (y:Year {year: row.year})
MERGE (m)-[r:RELEASED_IN]->(y)
} IN 2 CONCURRENT TRANSACTIONS OF 10 ROWS
The deadlock occurs because the two transactions are simultaneously trying to lock and merge the same Year
.
ForsetiClient[transactionId=64, clientId=12] can't acquire ExclusiveLock{owner=ForsetiClient[transactionId=63, clientId=9]} on NODE_RELATIONSHIP_GROUP_DELETE(98) because holders of that lock are waiting for ForsetiClient[transactionId=64, clientId=12].
Wait list:ExclusiveLock[
Client[63] waits for [ForsetiClient[transactionId=64, clientId=12]]]
ON ERROR CONTINUE
to ignore deadlocks and complete outer transactionLOAD CSV WITH HEADERS FROM 'https://data.neo4j.com/importing-cypher/movies.csv' AS row
CALL (row) {
MERGE (m:Movie {movieId: row.movieId})
MERGE (y:Year {year: row.year})
MERGE (m)-[r:RELEASED_IN]->(y)
} IN 2 CONCURRENT TRANSACTIONS OF 10 ROWS ON ERROR CONTINUE REPORT STATUS as status
WITH status
WHERE status.errorMessage IS NOT NULL
RETURN status.transactionId AS transaction, status.committed AS commitStatus, status.errorMessage AS errorMessage
+-----------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------+
| transaction | commitStatus | errorMessage |
+-----------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------+
| "neo4j-transaction-486" | FALSE | "ForsetiClient[transactionId=486, clientId=8] can't acquire ExclusiveLock{owner=ForsetiClient[transactionId=485, clientId=13]} on NODE_RELATIONSHIP_GROUP_DELETE(18) because holders of that lock are waiting for ForsetiClient[transactionId=486, clientId=8]. |
| | \ Wait list:ExclusiveLock[ |
| | \ Client[485] waits for [ForsetiClient[transactionId=486, clientId=8]]]" |
| "neo4j-transaction-486" | FALSE | "ForsetiClient[transactionId=486, clientId=8] can't acquire ExclusiveLock{owner=ForsetiClient[transactionId=485, clientId=13]} on NODE_RELATIONSHIP_GROUP_DELETE(18) because holders of that lock are waiting for ForsetiClient[transactionId=486, clientId=8]. |
| | \ Wait list:ExclusiveLock[ |
| | \ Client[485] waits for [ForsetiClient[transactionId=486, clientId=8]]]" |
| "neo4j-transaction-486" | FALSE | "ForsetiClient[transactionId=486, clientId=8] can't acquire ExclusiveLock{owner=ForsetiClient[transactionId=485, clientId=13]} on NODE_RELATIONSHIP_GROUP_DELETE(18) because holders of that lock are waiting for ForsetiClient[transactionId=486, clientId=8]. |
| | \ Wait list:ExclusiveLock[ |
| | \ Client[485] waits for [ForsetiClient[transactionId=486, clientId=8]]]" |
| "neo4j-transaction-486" | FALSE | "ForsetiClient[transactionId=486, clientId=8] can't acquire ExclusiveLock{owner=ForsetiClient[transactionId=485, clientId=13]} on NODE_RELATIONSHIP_GROUP_DELETE(18) because holders of that lock are waiting for ForsetiClient[transactionId=486, clientId=8]. |
| | \ Wait list:ExclusiveLock[ |
| | \ Client[485] waits for [ForsetiClient[transactionId=486, clientId=8]]]" |
| "neo4j-transaction-486" | FALSE | "ForsetiClient[transactionId=486, clientId=8] can't acquire ExclusiveLock{owner=ForsetiClient[transactionId=485, clientId=13]} on NODE_RELATIONSHIP_GROUP_DELETE(18) because holders of that lock are waiting for ForsetiClient[transactionId=486, clientId=8]. |
| | \ Wait list:ExclusiveLock[ |
| | \ Client[485] waits for [ForsetiClient[transactionId=486, clientId=8]]]" |
| "neo4j-transaction-486" | FALSE | "ForsetiClient[transactionId=486, clientId=8] can't acquire ExclusiveLock{owner=ForsetiClient[transactionId=485, clientId=13]} on NODE_RELATIONSHIP_GROUP_DELETE(18) because holders of that lock are waiting for ForsetiClient[transactionId=486, clientId=8]. |
| | \ Wait list:ExclusiveLock[ |
| | \ Client[485] waits for [ForsetiClient[transactionId=486, clientId=8]]]" |
| "neo4j-transaction-486" | FALSE | "ForsetiClient[transactionId=486, clientId=8] can't acquire ExclusiveLock{owner=ForsetiClient[transactionId=485, clientId=13]} on NODE_RELATIONSHIP_GROUP_DELETE(18) because holders of that lock are waiting for ForsetiClient[transactionId=486, clientId=8]. |
| | \ Wait list:ExclusiveLock[ |
| | \ Client[485] waits for [ForsetiClient[transactionId=486, clientId=8]]]" |
| "neo4j-transaction-486" | FALSE | "ForsetiClient[transactionId=486, clientId=8] can't acquire ExclusiveLock{owner=ForsetiClient[transactionId=485, clientId=13]} on NODE_RELATIONSHIP_GROUP_DELETE(18) because holders of that lock are waiting for ForsetiClient[transactionId=486, clientId=8]. |
| | \ Wait list:ExclusiveLock[ |
| | \ Client[485] waits for [ForsetiClient[transactionId=486, clientId=8]]]" |
| "neo4j-transaction-486" | FALSE | "ForsetiClient[transactionId=486, clientId=8] can't acquire ExclusiveLock{owner=ForsetiClient[transactionId=485, clientId=13]} on NODE_RELATIONSHIP_GROUP_DELETE(18) because holders of that lock are waiting for ForsetiClient[transactionId=486, clientId=8]. |
| | \ Wait list:ExclusiveLock[ |
| | \ Client[485] waits for [ForsetiClient[transactionId=486, clientId=8]]]" |
| "neo4j-transaction-486" | FALSE | "ForsetiClient[transactionId=486, clientId=8] can't acquire ExclusiveLock{owner=ForsetiClient[transactionId=485, clientId=13]} on NODE_RELATIONSHIP_GROUP_DELETE(18) because holders of that lock are waiting for ForsetiClient[transactionId=486, clientId=8]. |
| | \ Wait list:ExclusiveLock[ |
| | \ Client[485] waits for [ForsetiClient[transactionId=486, clientId=8]]]" |
+-----------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------+
These are transient errors, meaning that re-running the transactions may be successful.
To retry the any failed inner transactions, use the error option ON ERROR RETRY
, which retries any failing transactions until the maximum retry duration has been reached.
The following query uses ON ERROR RETRY … THEN CONTINUE
to retry the above query for a maximum of 3
seconds and then continue the execution of subsequent inner transactions by ignoring any recoverable errors.
ON ERROR RETRY … THEN CONTINUE
to retry deadlocked inner transactions and complete outer transactionLOAD CSV WITH HEADERS FROM 'https://data.neo4j.com/importing-cypher/movies.csv' AS row
CALL (row) {
MERGE (m:Movie {movieId: row.movieId})
MERGE (y:Year {year: row.year})
MERGE (m)-[r:RELEASED_IN]->(y)
} IN 2 CONCURRENT TRANSACTIONS OF 10 ROWS ON ERROR RETRY FOR 3 SECONDS THEN CONTINUE REPORT STATUS AS status
RETURN status.transactionID as transaction, status.committed AS successfulTransaction
The result shows that all transactions are now successful:
+-------------------------------------------------+
| transaction | successfulTransaction |
+-------------------------------------------------+
| "neo4j-transaction-500" | TRUE |
| "neo4j-transaction-500" | TRUE |
| "neo4j-transaction-500" | TRUE |
| "neo4j-transaction-500" | TRUE |
| "neo4j-transaction-500" | TRUE |
| "neo4j-transaction-500" | TRUE |
| "neo4j-transaction-500" | TRUE |
| "neo4j-transaction-500" | TRUE |
| "neo4j-transaction-500" | TRUE |
| "neo4j-transaction-500" | TRUE |
| "neo4j-transaction-501" | TRUE |
| "neo4j-transaction-501" | TRUE |
| "neo4j-transaction-501" | TRUE |
| "neo4j-transaction-501" | TRUE |
| "neo4j-transaction-501" | TRUE |
| "neo4j-transaction-501" | TRUE |
| "neo4j-transaction-501" | TRUE |
| "neo4j-transaction-501" | TRUE |
| "neo4j-transaction-501" | TRUE |
| "neo4j-transaction-501" | TRUE |
| "neo4j-transaction-502" | TRUE |
| "neo4j-transaction-502" | TRUE |
| "neo4j-transaction-502" | TRUE |
| "neo4j-transaction-502" | TRUE |
| "neo4j-transaction-502" | TRUE |
| "neo4j-transaction-502" | TRUE |
| "neo4j-transaction-502" | TRUE |
| "neo4j-transaction-502" | TRUE |
| "neo4j-transaction-502" | TRUE |
| "neo4j-transaction-502" | TRUE |
| "neo4j-transaction-504" | TRUE |
| "neo4j-transaction-504" | TRUE |
| "neo4j-transaction-504" | TRUE |
| "neo4j-transaction-504" | TRUE |
| "neo4j-transaction-504" | TRUE |
| "neo4j-transaction-504" | TRUE |
| "neo4j-transaction-504" | TRUE |
| "neo4j-transaction-504" | TRUE |
| "neo4j-transaction-504" | TRUE |
| "neo4j-transaction-504" | TRUE |
| "neo4j-transaction-503" | TRUE |
| "neo4j-transaction-503" | TRUE |
| "neo4j-transaction-503" | TRUE |
| "neo4j-transaction-503" | TRUE |
| "neo4j-transaction-503" | TRUE |
| "neo4j-transaction-503" | TRUE |
| "neo4j-transaction-503" | TRUE |
| "neo4j-transaction-503" | TRUE |
| "neo4j-transaction-503" | TRUE |
| "neo4j-transaction-503" | TRUE |
| "neo4j-transaction-505" | TRUE |
| "neo4j-transaction-505" | TRUE |
| "neo4j-transaction-505" | TRUE |
| "neo4j-transaction-505" | TRUE |
| "neo4j-transaction-505" | TRUE |
| "neo4j-transaction-505" | TRUE |
| "neo4j-transaction-505" | TRUE |
| "neo4j-transaction-505" | TRUE |
| "neo4j-transaction-505" | TRUE |
| "neo4j-transaction-505" | TRUE |
| "neo4j-transaction-506" | TRUE |
| "neo4j-transaction-506" | TRUE |
| "neo4j-transaction-506" | TRUE |
| "neo4j-transaction-506" | TRUE |
| "neo4j-transaction-506" | TRUE |
| "neo4j-transaction-506" | TRUE |
| "neo4j-transaction-506" | TRUE |
| "neo4j-transaction-506" | TRUE |
| "neo4j-transaction-506" | TRUE |
| "neo4j-transaction-506" | TRUE |
| "neo4j-transaction-507" | TRUE |
| "neo4j-transaction-507" | TRUE |
| "neo4j-transaction-507" | TRUE |
| "neo4j-transaction-507" | TRUE |
| "neo4j-transaction-507" | TRUE |
| "neo4j-transaction-507" | TRUE |
| "neo4j-transaction-507" | TRUE |
| "neo4j-transaction-507" | TRUE |
| "neo4j-transaction-507" | TRUE |
| "neo4j-transaction-507" | TRUE |
| "neo4j-transaction-508" | TRUE |
| "neo4j-transaction-508" | TRUE |
| "neo4j-transaction-508" | TRUE |
| "neo4j-transaction-508" | TRUE |
| "neo4j-transaction-508" | TRUE |
| "neo4j-transaction-508" | TRUE |
| "neo4j-transaction-508" | TRUE |
| "neo4j-transaction-508" | TRUE |
| "neo4j-transaction-508" | TRUE |
| "neo4j-transaction-508" | TRUE |
| "neo4j-transaction-509" | TRUE |
| "neo4j-transaction-509" | TRUE |
| "neo4j-transaction-509" | TRUE |
+-------------------------------------------------+
Restrictions
These are the restrictions on queries that use CALL { … } IN TRANSACTIONS
:
-
A nested
CALL { … } IN TRANSACTIONS
inside aCALL { … }
clause is not supported. -
A
CALL { … } IN TRANSACTIONS
in aUNION
is not supported. -
A
CALL { … } IN TRANSACTIONS
after a write clause is not supported, unless that write clause is inside aCALL { … } IN TRANSACTIONS
.