事务中的 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 在独立事务中创建节点。

friends.csv
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
已创建节点: 5
已设置属性: 10
已添加标签: 5
已提交事务: 1

由于此示例中的 CSV 文件较小,因此仅启动并提交了一个独立事务。

删除大量数据

使用 CALL { …​ } IN TRANSACTIONS 是删除大量数据的推荐方式。

示例 1. 对所有节点执行 DETACH DELETE
查询
MATCH (n)
CALL (n) {
  DETACH DELETE n
} IN TRANSACTIONS
结果

(空结果)

行数:0
已删除节点: 5
已删除关系: 2
已提交事务: 1

示例 2. 仅对部分节点执行 DETACH DELETE

CALL { …​ } IN TRANSACTIONS 子查询不应被修改。

任何必要的过滤操作都可以在子查询之前完成。

查询
MATCH (n:Label) WHERE n.prop > 100
CALL (n) {
  DETACH DELETE n
} IN TRANSACTIONS
结果

(空结果)

行数:0

分批处理是基于输入到 CALL { …​ } IN TRANSACTIONS 中的行进行的,因此数据必须从调用外部提供,以便分批生效。这就是为什么在上述示例中节点在子查询之外进行匹配的原因。如果 MATCH 子句位于子查询内部,数据删除将作为一个单一事务运行。

分批处理

每个独立事务要执行的工作量可以通过指定在提交当前事务并开始新事务之前处理多少输入行来定义。输入行数通过修饰符 OF n ROWS (或 OF n ROW) 设置。如果省略,默认批处理大小为 1000 行。行数可以使用任何计算结果为正整数且不引用节点或关系的表达式来表示。

此示例加载一个 CSV 文件,每 2 个输入行执行一次事务。

friends.csv
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
已创建节点: 5
已设置属性: 10
已添加标签: 5
已提交事务: 3

查询现在启动并提交了三个独立的事务。

  1. 子查询的前两次执行(针对 LOAD CSV 的前两个输入行)在第一个事务中进行。

  2. 第一个事务随后在继续下一步之前提交。

  3. 接下来的两次子查询执行(针对接下来的两个输入行)在第二个事务中进行。

  4. 第二个事务被提交。

  5. 最后一次子查询执行(针对最后一个输入行)在第三个事务中进行。

  6. 第三个事务被提交。

您还可以使用 CALL { …​ } IN TRANSACTIONS OF n ROWS 来批量删除所有数据,以避免大规模垃圾回收或 OutOfMemory 异常。例如:

查询
MATCH (n)
CALL (n) {
  DETACH DELETE n
} IN TRANSACTIONS OF 2 ROWS
结果

(空结果)

行数:0
已删除节点: 9
已删除关系: 2
已提交事务: 5

在一定范围内,使用更大的批处理大小性能更高。此处使用的 2 ROWS 仅是针对小数据集给出的示例。对于较大的数据集,您可能需要使用更大的批处理大小,例如 10000 ROWS

组合数据库

CALL { …​ } IN TRANSACTIONS 可以与 组合数据库 一起使用。

尽管组合数据库允许在单个查询中访问多个图,但在单个事务中只能修改一个图。CALL { …​ } IN TRANSACTIONS 提供了一种构建可以修改多个图的查询的方法。

虽然前面的示例通常适用于组合数据库,但在子查询中使用组合数据库时,还有一些额外的因素需要考虑。以下示例展示了如何在组合数据库上使用 CALL { …​ } IN TRANSACTIONS

示例 3. 在所有成员数据库上导入 CSV 文件
friends.csv
1,Bill,26
2,Max,27
3,Anna,22
4,Gladys,29
5,Summer,24
从 friends.csv 获取数据,在所有成员数据库上创建 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
示例 4. 从所有成员数据库中删除所有节点和关系
查询
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 处理过程中发生错误时,错误消息会包含已提交事务数量的信息。该信息在组合数据库上是不准确的,因为它总是报告 (Transactions committed: 0)

CALL { …​ } IN CONCURRENT TRANSACTIONS 目前无法为组合数据库提供额外的并行性。默认情况下,子查询会在每个目标图上并行执行。

组合数据库中的批处理大小

由于针对不同图的 CALL { …​ } IN TRANSACTIONS 子查询不能交叉执行,因此如果 USE 子句计算出的目标与当前目标不同,当前批次会被提交并创建下一个批次。

使用 IN TRANSACTIONS OF …​ ROWS 声明的批处理大小代表批处理大小的上限,但实际的批处理大小取决于按顺序针对同一个数据库的输入行数。每次目标数据库更改时,批次都会被提交。

示例 5. IN TRANSACTIONS OF ROWS 在组合数据库上的行为

下一个示例假设组合数据库 composite 存在两个成员 remoteGraph1remoteGraph2

虽然声明的批处理大小为 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 CONTINUEON ERROR BREAKON ERROR FAILON ERROR RETRY

如果发生错误,已成功提交的任何内部事务将保持不变,不会回滚。但是,任何失败的内部事务都将被完全回滚。无论使用哪种 ON ERROR 选项,此行为都适用。

ON ERROR CONTINUE

ON ERROR CONTINUE 会忽略可恢复的错误,并继续执行后续的内部事务。外部事务最终成功。当内部查询失败时,ON ERROR CONTINUE 确保外部事务继续运行,并为失败的内部查询返回 null

示例 6. 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
GQLSTATUS 错误链

22N28: 错误: 数据异常 - 溢出错误。'/ by zero' 操作的结果导致了溢出。

22012: 错误: 数据异常 - 除以零

由于失败发生在第一个事务提交之后,数据库保留了已成功创建的节点。

返回失败事务之前创建的节点
MATCH (e:Person)
RETURN e.num
结果
e.num

25

50

行: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

100

null

50

25

行: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

null

null

50

25

行:4

在这种情况下,第一个内部事务包含 i = 1i = 0。由于 i = 0 导致错误,整个事务回滚,导致两个元素的结果均为 null

ON ERROR BREAK

ON ERROR BREAK 会忽略可恢复的错误,并停止执行后续的内部事务。外部事务最终成功。当内部查询失败时,ON ERROR BREAK 确保外部事务继续,但会停止执行任何进一步的内部事务。失败内部查询预期的变量将被绑定为 null,适用于所有后续事务,包括失败的那一个。

示例 7. 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

100

null

null

null

行: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

null

null

null

null

行:4

ON ERROR FAIL

ON ERROR FAIL 确认可恢复的错误并停止执行后续的内部事务,导致外部事务失败。

如果不明确指定错误处理标志,ON ERROR FAIL 是默认行为。
示例 8. 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
GQLSTATUS 错误链

22N28: 错误: 数据异常 - 溢出错误。'/ by zero' 操作的结果导致了溢出。

22012: 错误: 数据异常 - 除以零

ON ERROR RETRY

ON ERROR RETRY 对因瞬态错误(即重试事务有望产生不同结果的错误)而失败的事务批次使用指数延迟进行重试,并具有可选的 最大重试持续时间。如果事务在最长持续时间后仍然失败,将根据可选的 回退错误处理模式THEN CONTINUE, THEN BREAK, THEN FAIL(默认))进行处理。

ON ERROR RETRY 通过处理瞬态错误来提高查询稳健性,无需人工干预。它特别适用于 并发事务,降低了因 死锁 而导致查询失败的可能性。

示例 9. 带默认持续时间的基本重试

下面的示例演示了一个基本的重试场景。如果 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> 是大于或等于 0INTEGERFLOAT 值,表示秒数。允许使用小数,并且可以通过参数设置 <duration>。请注意,此 <duration> 会覆盖默认值。

持续时间计时器在第一次重试安排时启动。因此,无论指定的持续时间如何,都至少会尝试一次重试。如果事务仍然因瞬态错误失败,除非持续时间已过期,否则将进行新的尝试。例如,值 0(或接近 0)会导致一次重试,但保证仅尝试一次。

示例 10. 设置重试的最长时间限制

在此示例中,重试持续时间明确设置为 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 是默认回退选项,因此不必明确指定。
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

状态报告

用户还可以通过 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

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" }

行: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

100

true

true

null

null

true

false

"/ by zero"

null

false

false

null

null

false

false

null

行: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 子查询
示例 11. 在并发事务中加载 CSV 文件

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

444

行:1

并发和非确定性结果

CALL { …​ } IN TRANSACTIONS 默认使用 有序 语义,批次按逐行顺序提交。例如,在 CALL { <I> } IN TRANSACTIONS 中,<I1> 执行中完成的任何写入都必须被 <I2> 等观察到。

相比之下,CALL { …​ } IN CONCURRENT TRANSACTIONS 使用 并发 语义,其中特定批次提交的行数和提交批次的顺序均未定义。也就是说,在 CALL { <I> } IN CONCURRENT TRANSACTIONS 中,<I1> 执行中提交的写入可能被也可能不被 <I2> 观察到。

因此,在并发事务中执行的 CALL 子查询的结果可能是不确定的。为保证结果确定,请确保已提交批次的结果彼此不依赖。

使用 CALL { …​ } IN CONCURRENT TRANSACTIONS 可能会影响 错误行为。具体而言,当使用 ON ERROR BREAKON ERROR FAIL 且一个事务失败时,任何并发事务 可能 不会被中断和回滚(尽管后续的会被回滚)。这是因为无法为并发事务提供时序保证。也就是说,正在进行的事务在处理错误的时间窗口内可能会也可能不会成功提交。请使用 状态报告 来确定哪些批次已提交,哪些失败或未启动。

死锁

当写事务发生时,Neo4j 会加锁以在更新时保持数据一致性。例如,在创建或删除关系时,会针对该特定关系及其连接的节点获取写锁。

当两个事务因试图同时修改对方锁定的节点或关系而被彼此阻塞时,就会发生死锁(有关 Neo4j 中锁和死锁的详细信息,请参阅 Operations Manual → Concurrent data access)。

在使用 CALL { …​ } IN CONCURRENT TRANSACTIONS 时,如果两个或多个批次的事务尝试以导致循环依赖的顺序获取相同的锁,则可能会发生死锁。如果是这样,受影响的事务总是会回滚,并抛出错误,除非查询附加了以下错误选项之一:

后者特别适用于并发事务,因为它会在重试之间进行指数退避,直到达到 最大重试持续时间

死锁检测和事务重试可能会非常耗时。当导入包含大量需要在相同节点之间合并但被不同批次处理的关系的数据时,增加并发性可能不会提高性能。相反,它可能会减慢导入过程。
示例 12. 处理死锁

以下查询尝试创建通过 RELEASED_IN 关系连接的 MovieYear 节点。注意 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

GQLSTATUS 错误链

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

限制

以下是使用 CALL { …​ } IN TRANSACTIONS 的查询限制:

  • 不支持 CALL { …​ } 子句内嵌套 CALL { …​ } IN TRANSACTIONS

  • 不支持在 UNION 中使用 CALL { …​ } IN TRANSACTIONS

  • 不支持在写子句之后使用 CALL { …​ } IN TRANSACTIONS,除非该写子句位于 CALL { …​ } IN TRANSACTIONS 内部。