额外的match子句会导致Neo4j查询非常慢

时间:2018-06-24 23:43:47

标签: neo4j

我有以下查询,这很慢:

MATCH (defn :WORKFLOW_DEFINITION { uid: '48a72b6b-6791-4da9-8d8f-dc4375d0fe2d' }),
(instance :WORKFLOW_INSTANCE :SUCCEEDED) -[:DEFINED_BY]-> (defn)
WITH instance, defn
ORDER BY instance.createdAt, instance.uid
LIMIT 1000

OPTIONAL MATCH (instance) -[:INPUT]-> (input :ARTIFACT) <-[:DEFINES_INPUT]- (inputDefn :WORKFLOW_ARTIFACT) <-[:TAKES_INPUT]- (defn)
WITH instance, defn,
  collect([input.uid, inputDefn.label, input.bucket, input.key, input.blobUid]) AS inputs

RETURN instance, defn, inputs

我的数据结构如下:

  • WORKFLOW_DEFINITION元素定义工作流程;每个都有 一对多的WORKFLOW_ARTIFACTs(关系为TAKES_INPUT)定义了定义所需要的不同输入。
  • WORKFLOW_INSTANCE元素是工作流定义的实例;他们通过DEFINED_BY链接到其父定义。
  • 每个WORKFLOW_INSTANCE都采用一对多(表示磁盘文件上的实际/现有文件)伪像(通过INPUT关系的边)。
  • 输入的ARTIFACT数WORKFLOW_INSTANCE 等于其父级的WORKFLOW_ARTIFACTs数 WORKFLOW_DEFINITION链接。
  • 每个输入ARTIFACT均精确链接至以下其中一个 父母的WORKFLOW_ARTIFACTs(WORKFLOW_ARTIFACT提供 运行工作流时如何消耗ARTIFACT的数据)

我目前有少量的WORKFLOW_DEFINITIONS。大多数WORKFLOW_DEFINITIONs都有一些WORKFLOW_INSTANCE,但是其中一个(我感兴趣的查询)有〜2000 WORKFLOW_INSTANCE。该定义包含三个WORKFLOW_ARTIFACT,因此,此定义的每个WORKFLOW_INSTANCE都有三个与WORKFLOW_ARTIFACT链接的ARTIFACT。

当我运行上面的查询时,它总共需要50449598个数据库命中,并且需要16654毫秒。

另一方面,下面的查询仅在第6行的末尾省略了'<-[:TAKES_INPUT]-(defn)',总共仅需要55598次db hit,耗时82毫秒。这是省略了该位的快速查询:

MATCH (defn :WORKFLOW_DEFINITION { uid: '48a72b6b-6791-4da9-8d8f-dc4375d0fe2d' }),
(instance :WORKFLOW_INSTANCE :SUCCEEDED) -[:DEFINED_BY]-> (defn)
WITH instance, defn
ORDER BY instance.createdAt, instance.uid
LIMIT 1000

OPTIONAL MATCH (instance) -[:INPUT]-> (input :ARTIFACT) <-[:DEFINES_INPUT]- (inputDefn :WORKFLOW_ARTIFACT)
WITH instance, defn,
  collect([input.uid, inputDefn.label, input.bucket, input.key, input.blobUid]) AS inputs

RETURN instance, defn, inputs

以下是速度缓慢的概要分析查询计划: Slow query plan

并快速: Fast query plan

为什么最后一个边缘回到WORKFLOW_DEFINITION(以确保我们得到正确的WORKFLOW_ARTIFACT,因为ARTIFACT可能用于不同的工作流中)使查询如此缓慢?这个看起来像组合爆炸的数据库命中数是哪里来的?

在此先感谢您的帮助,如果您还有其他需要说明的地方,请告诉我!

1 个答案:

答案 0 :(得分:0)

看看这两个查询计划,查询计划处理扩展的方式有所不同。

如果您查看较慢的查询计划中的操作流程,则会发现它从defn扩展到inputDefn,然后从inputDefn扩展到input,最后是从instanceinput的Expand(Into)。

速度更快的查询在开始处理右侧时不会考虑defn(因为它不参与OPTIONAL MATCH的任何扩展),因此不能使用相同的方法。相反,它从另一个方向扩展:从instance扩展到input,然后从input扩展到inputDefn。看起来使用该方向进行遍历最终会更加高效。

要点是,从inputinputDefn的遍历是有效的,它看起来是1-1相关。但是,inputDefninput似乎是一个可怕的扩展,看起来inputDefn往往是超节点,每个与input节点的关系平均为2400。

关于如何解决此问题,虽然没有计划者提示明确地避免朝某个方向扩展,但我们也许可以使用join hints来推动计划者实现这一目标。 (您可以尝试对不同变量使用联接提示,并解释计划以确保避免将inputDefn扩展为input)。

如果我们添加以下提示:USING JOIN ON inputDefn,则意味着我们将从双方向inputDefn扩展并进行散列连接以获得最终结果集。这样可以避免代价高昂的从inputDefninput的扩展。

MATCH (defn :WORKFLOW_DEFINITION { uid: '48a72b6b-6791-4da9-8d8f-dc4375d0fe2d' }),
(instance :WORKFLOW_INSTANCE :SUCCEEDED) -[:DEFINED_BY]-> (defn)
WITH instance, defn
ORDER BY instance.createdAt, instance.uid
LIMIT 1000

OPTIONAL MATCH (instance) -[:INPUT]-> (input :ARTIFACT) <-[:DEFINES_INPUT]- (inputDefn :WORKFLOW_ARTIFACT) <-[:TAKES_INPUT]- (defn)
USING JOIN ON inputDefn
WITH instance, defn,
  collect([input.uid, inputDefn.label, input.bucket, input.key, input.blobUid]) AS inputs

RETURN instance, defn, inputs