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:

friends.csv
1,Bill,26
2,Max,27
3,Anna,22
4,Gladys,29
5,Summer,24
Query
LOAD CSV FROM 'file:///friends.csv' AS line
CALL (line) {
  CREATE (:Person {name: line[1], age: toInteger(line[2])})
} IN TRANSACTIONS
Table 1. Result

(empty result)

Rows: 0
Nodes created: 5
Properties set: 10
Labels added: 5
Transactions committed: 1

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.

Example 1. DETACH DELETE on all nodes
Query
MATCH (n)
CALL (n) {
  DETACH DELETE n
} IN TRANSACTIONS
Table 2. Result

(empty result)

Rows: 0
Nodes deleted: 5
Relationships deleted: 2
Transactions committed: 1

Example 2. DETACH DELETE on only some nodes

The CALL { …​ } IN TRANSACTIONS subquery should not be modified.

Any necessary filtering can be done before the subquery.

Query
MATCH (n:Label) WHERE n.prop > 100
CALL (n) {
  DETACH DELETE n
} IN TRANSACTIONS
Table 3. Result

(empty result)

Rows: 0

The batching is performed on the input rows fed into CALL { …​ } IN TRANSACTIONS, so the data must be supplied from outside the call in order for the batching to have an effect. That is why the nodes are matched outside the subqueries in the examples above. If the MATCH clause were inside the subquery, the data deletion would run as one single transaction.

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:

friends.csv
1,Bill,26
2,Max,27
3,Anna,22
4,Gladys,29
5,Summer,24
Query
LOAD CSV FROM 'file:///friends.csv' AS line
CALL (line) {
  CREATE (:Person {name: line[1], age: toInteger(line[2])})
} IN TRANSACTIONS OF 2 ROWS
Table 4. Result

(empty result)

Rows: 0
Nodes created: 5
Properties set: 10
Labels added: 5
Transactions committed: 3

The query now starts and commits three separate transactions:

  1. The first two executions of the subquery (for the first two input rows from LOAD CSV) take place in the first transaction.

  2. The first transaction is then committed before proceeding.

  3. The next two executions of the subquery (for the next two input rows) take place in a second transaction.

  4. The second transaction is committed.

  5. The last execution of the subquery (for the last input row) takes place in a third transaction.

  6. 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:

Query
MATCH (n)
CALL (n) {
  DETACH DELETE n
} IN TRANSACTIONS OF 2 ROWS
Table 5. Result

(empty result)

Rows: 0
Nodes deleted: 9
Relationships deleted: 2
Transactions committed: 5

Up to a point, using a larger batch size will be more performant. The batch size of 2 ROWS is an example given the small data set used here. For larger data sets, you might want to use larger batch sizes, such as 10000 ROWS.

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.

Example 3. Import a CSV file on all constituents
friends.csv
1,Bill,26
2,Max,27
3,Anna,22
4,Gladys,29
5,Summer,24
Create Person nodes on all constituents, drawing data from friends.csv
UNWIND 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
Example 4. Remove all nodes and relationships from all constituents
Query
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 CALL { …​ } IN TRANSACTIONS processing, the error message includes information about how many transactions have been committed. That information is inaccurate on composite databases, as it always reports (Transactions committed: 0).

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.

Example 5. Behavior of IN TRANSACTIONS OF ROWS on composite databases

The 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.

Query
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.

Example 6. ON ERROR CONTINUE

In the below query, the last subquery execution in the second inner transaction fails due to division by zero:

Subquery with failing transaction
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
Error message
/ by zero (Transactions committed: 1)

Since the failure occurred after the first transaction was committed, the database retains the successfully created nodes.

Return nodes created prior to failed transaction
MATCH (e:Person)
RETURN e.num
Table 6. Result
e.num

25

50

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:

Transactions batched in 1 row 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
RETURN n.num
Table 7. Result
n.num

100

null

50

25

Rows: 4

Note the difference in results when batching in transactions of 2 rows:

Transactions batched in 2 rows with 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
Table 8. Result
n.num

null

null

50

25

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.

Example 7. 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.

Transactions batched in 1 row 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
RETURN n.num
Table 9. Result
n.num

100

null

null

null

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.

Transactions batched in 2 rows with 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
Table 10. Result
n.num

null

null

null

null

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.
Example 8. 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.

Transactions batched in 1 row with 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
Error message
/ 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.
Example 9. Basic retry with default duration

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.

Example 10. Set a maximum time limit for retries

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 duration
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 2.5 SECONDS
Parameter
{
  "duration": 10
}
ON ERROR RETRY using a parameter for the duration
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 $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, and null will be returned for any failed inner transactions. See ON 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, and null will be returned for the failed inner transaction and all subsequent ones. See ON 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. See ON ERROR FAIL for more information about this behavior.

Because THEN FAIL is the default fallback option it does not have to be specified.
ON ERROR RETRY 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 CONTINUE
ON ERROR RETRY 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 BREAK
ON ERROR RETRY THEN FAIL
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, or null if the transaction was not started.

  • errorMessage, the inner transaction error message, or null in case of no error.

Example of reporting status with ON ERROR CONTINUE:

Query
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
Table 11. Result
n.num s

100

{"committed": true, "errorMessage": null, "started": true, "transactionId": "neo4j-transaction-835" }

null

{"committed": false, "errorMessage": "/ by zero", "started": true, "transactionId": "neo4j-transaction-836" }

50

{"committed": true, "errorMessage": null, "started": true, "transactionId": "neo4j-transaction-837" }

25

{"committed": true, "errorMessage": null, "started": true, "transactionId": "neo4j-transaction-838" }

Rows: 4

Example of reporting status with ON ERROR BREAK:

Query
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
Table 12. Result
n.num s.started s.committed s.errorMessage

100

true

true

null

null

true

false

"/ by zero"

null

false

false

null

null

false

false

null

Rows: 4

Reporting status with ON ERROR FAIL is disallowed:

Query
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
Error
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.
Example 11. Load a CSV file in concurrent transactions

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
Table 13. Result
personNodes

444

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:

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.
Example 12. Dealing with deadlocks

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.

Query with concurrent transaction causing a deadlock
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.

Error message
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]]]
Query using ON ERROR CONTINUE to ignore deadlocks and complete outer transaction
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 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
Result
+-----------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------+
| 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.

Query using ON ERROR RETRY …​ THEN CONTINUE to retry deadlocked inner transactions and complete outer transaction
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 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:

Result
+-------------------------------------------------+
| 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 a CALL { …​ } clause is not supported.

  • A CALL { …​ } IN TRANSACTIONS in a UNION is not supported.

  • A CALL { …​ } IN TRANSACTIONS after a write clause is not supported, unless that write clause is inside a CALL { …​ } IN TRANSACTIONS.