事务中的 CALL 子查询
CALL
子查询可以在单独的内部事务中执行,生成中间提交。这在执行大量写入操作(如批处理更新、导入和删除)时非常有用。
要在单独的事务中执行 CALL
子查询,请在子查询后添加修饰符 IN TRANSACTIONS
。将打开一个外部事务以报告内部事务(创建和删除的节点、关系等)的累积统计信息,并且它将根据这些内部事务的结果成功或失败。默认情况下,内部事务将 1000 行分组。取消外部事务也会取消内部事务。
CALL { … } IN TRANSACTIONS 仅允许在隐式事务中使用。如果您正在使用Neo4j Browser,您必须在任何使用 CALL { … } IN TRANSACTIONS 的查询前加上 :auto 。 |
本页的示例使用变量作用域子句(在 Neo4j 5.23 中引入)将变量导入到 CALL 子查询中。如果您使用的是较旧版本的 Neo4j,请改用导入 WITH 子句。 |
语法
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}]}]
加载 CSV 数据
此示例使用 CSV 文件和 LOAD CSV
子句将数据导入数据库。它使用 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
|
行数:0 |
由于此示例中的 CSV 文件大小较小,因此仅启动并提交一个单独的事务。
删除大量数据
使用 CALL { … } IN TRANSACTIONS
是删除大量数据的推荐方式。
MATCH (n)
CALL (n) {
DETACH DELETE n
} IN TRANSACTIONS
|
行数:0 |
CALL { … } IN TRANSACTIONS
子查询不应被修改。
任何必要的过滤都可以在子查询之前完成。
MATCH (n:Label) WHERE n.prop > 100
CALL (n) {
DETACH DELETE n
} IN TRANSACTIONS
|
行数:0 |
批处理是根据输入到 |
批处理
每个单独事务中要完成的工作量可以指定为在提交当前事务并启动新事务之前要处理的输入行数。输入行数使用修饰符 OF n ROWS
(或 OF n ROW
)设置。如果省略,默认批处理大小为 1000
行。行数可以使用任何评估为正整数且不引用节点或关系的表达式来表示。
此示例加载一个 CSV 文件,每 2
行输入启动一个事务
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
|
行数:0 |
查询现在启动并提交了三个单独的事务
-
子查询的前两次执行(对于
LOAD CSV
的前两行输入)在第一个事务中进行。 -
然后,第一个事务在继续之前提交。
-
子查询的接下来的两次执行(对于接下来的两行输入)在第二个事务中进行。
-
第二个事务已提交。
-
子查询的最后一次执行(对于最后一行输入)在第三个事务中进行。
-
第三个事务已提交。
您还可以使用 CALL { … } IN TRANSACTIONS OF n ROWS
分批删除所有数据,以避免巨大的垃圾回收或 OutOfMemory
异常。例如
MATCH (n)
CALL (n) {
DETACH DELETE n
} IN TRANSACTIONS OF 2 ROWS
|
行数:0 |
在一定程度上,使用更大的批处理大小会更高效。鉴于此处使用的小数据集,批处理大小为 |
复合数据库
从 Neo4j 5.18 开始,CALL { … } IN TRANSACTIONS
可以与复合数据库一起使用。
尽管复合数据库允许在单个查询中访问多个图,但单个事务中只能修改一个图。CALL { … } IN TRANSACTIONS
提供了一种构建修改多个图的查询的方法。
虽然之前的示例通常适用于复合数据库,但在子查询中使用复合数据库时,还有一些额外的因素需要考虑。以下示例展示了如何在复合数据库上使用 CALL { … } IN TRANSACTIONS
。
1,Bill,26
2,Max,27
3,Anna,22
4,Gladys,29
5,Summer,24
Person
节点,从 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
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
由于批处理是根据输入到 CALL { … } IN TRANSACTIONS 的行执行的,因此数据必须从子查询外部提供,以便批处理生效。这就是为什么节点是在实际删除数据的子查询*之前*的子查询中匹配的。如果 MATCH 子句在第二个子查询内部,数据删除将作为单个事务运行。 |
目前存在一个已知问题。当 |
复合数据库中的批处理大小
因为针对不同图的 CALL { … } IN TRANSACTIONS
子查询不能交错执行,如果 USE
子句评估的目标与当前目标不同,则当前批次将被提交并创建下一个批次。
使用 IN TRANSACTIONS OF … ROWS
声明的批处理大小表示批处理大小的上限,但实际的批处理大小取决于有多少输入行*按顺序*针对一个数据库。每次目标数据库更改时,批处理都会被提交。
IN TRANSACTIONS OF ROWS
在复合数据库上的行为下一个示例假设复合数据库 composite
存在两个组成部分 remoteGraph1
和 remoteGraph2
。
虽然声明的批处理大小是 3,但只有前 2 行作用于 composite.remoteGraph1
,因此第一个事务的批处理大小是 2。接着是 3 行作用于 composite.remoteGraph2
,1 行作用于 composite.remoteGraph2
,最后 2 行作用于 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
错误行为
如果任何内部事务中发生错误,CALL { … } IN TRANSACTIONS
有四种不同的行为选项:ON ERROR CONTINUE
、ON ERROR BREAK
、ON ERROR FAIL
和 ON ERROR RETRY
。
如果发生错误,任何成功提交的内部事务保持不变且不会回滚。然而,任何失败的内部事务将完全回滚。无论使用哪种 ON ERROR 选项,此行为都适用。 |
ON ERROR CONTINUE
ON ERROR CONTINUE
忽略可恢复错误并继续执行后续的内部事务。外部事务成功。当内部查询失败时,ON ERROR CONTINUE
确保外部事务继续,并为失败的内部查询返回 null
。
ON ERROR CONTINUE
在下面的查询中,第二个内部事务的最后一次子查询执行因除零而失败
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)
由于故障发生在第一个事务提交之后,数据库保留了成功创建的节点。
MATCH (e:Person)
RETURN e.num
e.num |
---|
|
|
行数:2 |
在以下示例中,在内部事务失败后使用 ON ERROR CONTINUE
来执行剩余的内部事务,而不使外部事务失败
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 |
---|
|
|
|
|
行数:4 |
请注意事务以 2 行批处理时结果的差异
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 |
---|
|
|
|
|
行数:4 |
在这种情况下,第一个内部事务包括 i = 1
和 i = 0
。由于 i = 0
导致错误,整个事务被回滚,导致两个元素都为 null
。
ON ERROR BREAK
ON ERROR BREAK
忽略可恢复错误并停止执行后续的内部事务。外部事务成功。当内部查询失败时,ON ERROR BREAK
确保外部事务继续但停止执行任何进一步的内部事务。来自失败的内部查询的预期变量将绑定为 null
,适用于所有后续事务,包括失败的事务。
ON ERROR BREAK
在此示例中,ON ERROR BREAK
确保一旦内部事务失败 (i = 0
),就不会执行进一步的内部事务,而外部事务仍保持成功。
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 |
---|
|
|
|
|
行数:4 |
当事务以 2 行批处理时,第一个事务 (i = 1, 0
) 在 i = 0
处遇到错误,导致执行立即停止。结果,所有剩余的事务也返回 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 |
---|
|
|
|
|
行数:4 |
ON ERROR FAIL
ON ERROR FAIL
识别可恢复错误并停止后续内部事务的执行,导致外部事务失败。
如果未明确指定错误处理标志,ON ERROR FAIL 是默认行为。 |
ON ERROR FAIL
在以下示例中,ON ERROR FAIL
在内部事务失败后使用,以阻止剩余内部事务的执行,并导致外部事务也失败。
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
在因临时错误(即,重试事务可以预期会产生不同结果的错误)而失败的事务批次之间使用指数延迟进行重试,并可选择指定最大重试持续时间。如果事务在最大持续时间后仍然失败,则根据可选指定的回退错误处理模式(THEN CONTINUE
、THEN BREAK
、THEN FAIL
(默认))进行处理。
使用 ON ERROR RETRY 的查询目前只能使用槽式运行时。 |
下面的示例演示了一个基本重试场景。如果在创建 User
节点时发生临时错误,事务将以默认最大重试持续时间(30
秒)进行重试。如果重试成功,查询将继续。
如果重试在默认持续时间后失败,则查询失败,因为它行为类似于ON ERROR FAIL
(默认回退)。
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
指定最大重试持续时间
默认的最大重试持续时间为 30
秒。可以使用dbms.cypher.transactions.default_subquery_retry_timeout
设置新的默认值。
可以为单个查询指定最大重试持续时间,如下所示:ON ERROR RETRY [FOR] <duration> SEC[OND[S]]
,其中 <duration> 是表示秒的 INTEGER
或 FLOAT
值,且大于或等于 0
。允许使用小数,并且 <duration>
可以通过参数设置。请注意,此 <duration>
会覆盖默认值。
持续时间计时器在第一次重试计划时开始。因此,无论指定的持续时间如何,至少会尝试一次重试。如果事务仍然因临时错误而失败,除非持续时间已过期,否则将进行新的尝试。例如,值 0
(或接近 0
)将导致重试,但保证只尝试一次重试。
在此示例中,重试持续时间显式设置为 2.5
秒。这意味着事务将一直重试,直到成功或直到 2.5
秒过去。
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 FOR 2.5 SECONDS
{
"duration": 10
}
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 FOR $duration SECONDS
回退错误处理选项
ON ERROR RETRY
可以与通过 THEN
子句的其他ON ERROR
选项结合使用,以指定回退行为。回退行为指定如果事务未在时间限制内成功会发生什么。具体来说
-
ON ERROR RETRY … THEN CONTINUE
:查询将忽略可恢复错误并继续执行后续内部事务。外部事务成功,并且对于任何失败的内部事务将返回null
。有关此行为的更多信息,请参阅ON ERROR CONTINUE
。 -
ON ERROR RETRY … THEN BREAK
:查询将忽略可恢复错误并停止执行后续内部事务。外部事务成功,并且对于失败的内部事务和所有后续事务将返回null
。有关此行为的更多信息,请参阅ON ERROR BREAK
。 -
ON ERROR RETRY … THEN FAIL
(默认):查询将确认可恢复错误并停止后续内部事务的执行,导致外部事务失败。有关此行为的更多信息,请参阅ON ERROR FAIL
。
因为 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 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
状态报告
用户还可以通过使用 REPORT STATUS AS var
来报告内部事务的执行状态。此标志不允许用于 ON ERROR FAIL
。有关更多信息,请参阅错误行为。
在内部查询的每次执行完成(无论成功与否)后,都会创建一个状态值,记录有关执行及其执行事务的信息
-
如果内部执行产生一行或多行输出,则此状态值的绑定将添加到每一行中,在选定的变量名下。
-
如果内部执行失败,则会生成一行,其中包含在选定变量下的此状态值的绑定,以及所有应由内部查询返回的变量(如果有)的空绑定。
状态值是一个包含以下字段的映射值
-
started
:内部事务启动时为true
,否则为false
。 -
committed
:内部事务更改成功提交时为true
,否则为false
。 -
transactionId
:内部事务 ID,如果事务未启动则为null
。 -
errorMessage
:内部事务错误消息,如果没有错误则为null
。
使用 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 |
---|---|
|
|
|
|
|
|
|
|
行数:4 |
使用 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 |
---|---|---|---|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
行数:4 |
不允许使用 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
REPORT STATUS AS s
RETURN n.num, s.errorMessage
REPORT STATUS can only be used when specifying ON ERROR CONTINUE or ON ERROR BREAK
并发事务
默认情况下,CALL { … } IN TRANSACTIONS
是单线程的;一个 CPU 核心用于顺序执行批处理。
但是,CALL
子查询也可以通过附加 IN [n] CONCURRENT TRANSACTIONS
来并行执行批处理,其中 n
是用于设置可以并行执行的最大事务数的并发值。这允许 CALL
子查询同时利用多个 CPU 核心,这可以显著减少执行大型外部事务所需的时间。
并发值是可选的。如果未指定,将根据可用 CPU 核心的数量选择默认值。如果指定负数(只能通过参数完成),则并发数将是可用 CPU 核心数减去该数字的绝对值。 |
CALL { … } IN CONCURRENT TRANSACTIONS
特别适用于导入没有依赖关系的数据。此示例从 CSV 文件中分配给每个 Person 行的唯一 tmdbId
值(总共 444 个)创建 Person
节点,在 3 个并发事务中进行。
CONCURRENT TRANSACTIONS
中运行的 CALL
子查询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 |
---|
|
行数:1 |
并发和非确定性结果
CALL { … } IN TRANSACTIONS
默认使用*有序*语义,其中批处理按顺序逐行提交。例如,在 CALL { <I> } IN TRANSACTIONS
中,<I1>
执行中完成的任何写入都必须被 <I2>
观察到,依此类推。
相反,CALL { … } IN CONCURRENT TRANSACTIONS
使用*并发*语义,其中特定批次提交的行数和已提交批次的顺序是未定义的。也就是说,在 CALL { <I> } IN CONCURRENT TRANSACTIONS
中,<I1>
执行中提交的写入可能会或可能不会被 <I2>
观察到,依此类推。
因此,在并发事务中执行的 CALL
子查询的结果可能不是确定性的。为了保证确定性结果,请确保已提交批次的结果彼此不依赖。
死锁
当发生写事务时,Neo4j 会获取锁以在更新时保持数据一致性。例如,在创建或删除关系时,将对特定关系及其连接的节点获取写锁。
当两个事务相互阻塞时,就会发生死锁,因为它们试图同时修改一个被另一个事务锁定的节点或关系(有关 Neo4j 中锁和死锁的更多信息,请参阅操作手册 → 并发数据访问)。
当使用 CALL { … } IN CONCURRENT TRANSACTIONS
时,如果两个或更多批次的事务试图以导致它们之间循环依赖的顺序获取相同的锁,则可能会发生死锁。如果发生这种情况,受影响的事务总是回滚,并抛出错误,除非查询附加了以下错误选项之一
-
ON ERROR RETRY
在 Neo4j 2025.03 中引入
后者特别适用于并发事务,因为它会以指数退避的方式重试可恢复的瞬时错误,直到达到最大重试持续时间。
死锁检测和事务重试可能非常耗时。当导入包含大量需要在相同节点之间合并但分批处理的关系的数据时,增加并发性可能不会提高性能。相反,它可能会减慢导入过程。 |
以下查询尝试创建通过 RELEASED_IN
关系连接的 Movie
和 Year
节点。请注意,CSV 文件中只有三个不同的年份,这意味着应该只创建三个 Year
节点。
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
死锁发生是因为这两个事务同时尝试锁定和合并相同的 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
忽略死锁并完成外部事务的查询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
+-----------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------+
| 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]]]" |
+-----------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------+
这些是临时错误,这意味着重新运行事务可能会成功。要重试任何失败的内部事务,请使用错误选项 ON ERROR RETRY
,它会重试任何失败的事务,直到达到最大重试持续时间。
以下查询使用 ON ERROR RETRY … THEN CONTINUE
重试上述查询最多 3
秒,然后通过忽略任何可恢复错误来继续执行后续内部事务。
ON ERROR RETRY … THEN CONTINUE
重试死锁内部事务并完成外部事务的查询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
结果显示所有事务现在都成功了
+-------------------------------------------------+
| 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 |
+-------------------------------------------------+
死锁解决和事务重试是耗时的,因此死锁避免是首选策略。这可以通过将任务分成两个不同的子查询来实现:一个数据独立的子查询以最大并发并行运行,另一个数据依赖的子查询串行执行(即一次一个事务)以避免死锁。在下面的示例中,节点和属性是在并发子查询中创建的,而连接这些节点的关系是在串行子查询中创建的。这种方法既受益于并发事务的性能,又避免了死锁。
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})
RETURN m, y
} IN CONCURRENT TRANSACTIONS OF 10 ROWS ON ERROR RETRY THEN CONTINUE REPORT STATUS AS nodeStatus
CALL (m, y) {
MERGE (m)-[r:RELEASED_IN]->(y)
} IN TRANSACTIONS OF 10 ROWS ON ERROR RETRY THEN CONTINUE REPORT STATUS AS relationshipStatus
RETURN nodeStatus.transactionId as nodeTransaction,
nodeStatus.committed AS successfulNodeTransaction,
relationshipStatus.transactionId as relationshipTransaction,
relationshipStatus.committed AS successfulRelationshipTransaction
单击查看不使用 ON ERROR RETRY
重试失败事务的示例
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 *
WHERE status.committed = false
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 FAIL