事务中的 CALL 子查询
CALL 子查询可以配置为在独立的内部事务中执行,从而产生中间提交。这在进行大规模写操作(如批量更新、导入和删除)时非常有用。
若要在独立事务中执行 CALL 子查询,请在子查询后添加 IN TRANSACTIONS 修饰符。系统会打开一个外部事务,用于报告内部事务的累计统计信息(已创建和已删除的节点、关系等),且该外部事务的成功或失败取决于这些内部事务的结果。默认情况下,内部事务将每 1000 行数据分为一组。取消外部事务也会取消相应的内部事务。
CALL { … } IN TRANSACTIONS 仅允许在 隐式事务 中使用。如果您使用的是 Neo4j Browser,则必须在使用 CALL { … } IN TRANSACTIONS 的查询前添加 :auto。 |
本页示例使用 变量作用域子句 将变量导入到 CALL 子查询中。 |
语法
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 |
|
在一定范围内,使用更大的批处理大小性能更高。此处使用的 |
组合数据库
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 节点。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。随后 composite.remoteGraph2 有 3 行,composite.remoteGraph2 有 1 行,最后 composite.remoteGraph1 有 2 行。
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
22N28: 错误: 数据异常 - 溢出错误。'/ by zero' 操作的结果导致了溢出。 22012: 错误: 数据异常 - 除以零 |
由于失败发生在第一个事务提交之后,数据库保留了已成功创建的节点。
MATCH (e:Person)
RETURN e.num
| e.num |
|---|
|
|
行:2 |
在以下示例中,ON ERROR CONTINUE 用于在内部事务失败后继续执行剩余的内部事务,而不使外部事务失败。
ON ERROR CONTINUE 按每 1 行进行分批事务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 按每 2 行进行分批事务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 按每 1 行进行分批事务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 按每 2 行进行分批事务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 按每 1 行进行分批事务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
22N28: 错误: 数据异常 - 溢出错误。'/ by zero' 操作的结果导致了溢出。 22012: 错误: 数据异常 - 除以零 |
ON ERROR RETRY
ON ERROR RETRY 对因瞬态错误(即重试事务有望产生不同结果的错误)而失败的事务批次使用指数延迟进行重试,并具有可选的 最大重试持续时间。如果事务在最长持续时间后仍然失败,将根据可选的 回退错误处理模式(THEN CONTINUE, THEN BREAK, THEN FAIL(默认))进行处理。
下面的示例演示了一个基本的重试场景。如果 User 节点创建期间发生瞬态错误,事务将在默认的最长重试持续时间(30 秒)内进行重试。如果重试成功,查询将继续。
如果重试在默认持续时间后失败,查询将失败,因为它表现得像 ON ERROR FAIL(默认回退行为)。
ON ERROR RETRYUNWIND 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> 是大于或等于 0 的 INTEGER 或 FLOAT 值,表示秒数。允许使用小数,并且可以通过参数设置 <duration>。请注意,此 <duration> 会覆盖默认值。
持续时间计时器在第一次重试安排时启动。因此,无论指定的持续时间如何,都至少会尝试一次重试。如果事务仍然因瞬态错误失败,除非持续时间已过期,否则将进行新的尝试。例如,值 0(或接近 0)会导致一次重试,但保证仅尝试一次。
在此示例中,重试持续时间明确设置为 2.5 秒。这意味着事务将不断重试,直到成功或 2.5 秒过去。
ON ERROR RETRYUNWIND 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 RETRYUNWIND 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 中不允许使用。有关更多信息,请参阅 错误行为。
在每次内部查询执行完成(无论成功与否)后,会创建一个状态值,记录关于该执行和执行该操作的事务的信息。
-
如果内部执行产生一行或多行输出,则在每个行中添加对此状态值的绑定,名称为所选变量名。
-
如果内部执行失败,则产生一行,包含该选定变量对此状态值的绑定,以及所有应由内部查询返回的变量(如有)的 null 绑定。
状态值是一个包含以下字段的 Map:
-
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 核心数减去该数字的绝对值。
使用 CONCURRENT TRANSACTIONS 的查询目前只能使用 slotted runtime。 |
在组合数据库上使用 CONCURRENT TRANSACTIONS 时,查询执行没有额外的并行性。有关更多信息,请参阅 针对组合数据库的事务中的 CALL 子查询。 |
CALL { … } IN CONCURRENT TRANSACTIONS 特别适用于无依赖关系的数据导入。此示例通过 CSV 文件中每个人员行分配的唯一 tmdbId 值(共 444 个)在 3 个并发事务中创建 Person 节点。
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 中锁和死锁的详细信息,请参阅 Operations Manual → Concurrent data access)。
在使用 CALL { … } IN CONCURRENT TRANSACTIONS 时,如果两个或多个批次的事务尝试以导致循环依赖的顺序获取相同的锁,则可能会发生死锁。如果是这样,受影响的事务总是会回滚,并抛出错误,除非查询附加了以下错误选项之一:
后者特别适用于并发事务,因为它会在重试之间进行指数退避,直到达到 最大重试持续时间。
| 死锁检测和事务重试可能会非常耗时。当导入包含大量需要在相同节点之间合并但被不同批次处理的关系的数据时,增加并发性可能不会提高性能。相反,它可能会减慢导入过程。 |
以下查询尝试创建通过 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。
50N05: 错误: 常规处理异常 - 检测到死锁。在尝试获取锁时检测到死锁。更多详细信息请参阅日志。 |
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