知识库

了解查询计划缓存

当第一次提交 Cypher 语句时,Neo4j 将尝试确定查询是否在计划缓存中,然后再对其进行规划。默认情况下,Neo4j 将根据 dbms.query_cache_sizeconf/neo4j.conf 参数,在缓存中保留 1000 个查询计划。实际上,这实际上代表了 2 个查询计划缓存。

  • 字符串缓存

当 Cypher 最初被提交时,Cypher 语句将对字符串本身进行哈希计算。使用此生成的哈希值,我们将尝试确定语句是否已存在于计划缓存中,如果存在,则可能不需要重新规划。

但是请注意,在逻辑上相同但在大小写上不同的语句将生成不同的哈希值。以下 2 个语句虽然在语义上等效,但会生成不同的哈希值,可能需要重新规划。

match (n) return count(n);
MATCH (n) return COUNT(n);

此外,在逻辑上相同但在空格/回车符上不同的语句将生成不同的哈希值。以下 3 个语句将生成不同的哈希值,可能需要重新规划

MATCH (n) return COUNT(n);
MATCH (n) return      COUNT(n);

MATCH (n)
return COUNT(n);

PROFILE/EXPLAIN 为前缀的 Cypher 语句将在语句被哈希之前将其 PROFILE/EXPLAIN 删除。以下 2 个语句将哈希到相同的值

MATCH (n) return COUNT(n);
PROFILE MATCH (n) return COUNT(n);

如果 Cypher 语句的哈希值在第一个缓存中找不到,Neo4j 将尝试确定它是否在第二个缓存中。

  • AST 缓存

Neo4j 编译器将查询从字符串解析为抽象语法树 (AST),它是一个查询的 object 表示形式。优化器然后对语句进行规范化,以使规划更容易。例如

match (n:Person {id:101}) return n;

将规范化为

match (n:Person) where n.id={param1} return n;   {param1: 101}

在这种情况下,Neo4j 已将谓词 {id:101}MATCH 模式移动到 WHERE 子句,并将 101 值参数化为参数,例如 n.id={param1}。参数的使用在 这里 有更详细的说明

AST 不存储空格和关键字的大小写等信息,并且由于它已参数化,因此字面值可以更改,但仍然生成相同的 AST。

第二个查询缓存以这个规范化的 AST 为键。即这些查询将重用相同的查询计划。

match (n:Person) where n.id=101 return n;
match (n:Person {id:101}) return n;

MATCH ( n:Person { id : 101 } )
RETURN n;

最后,如果 Cypher 语句在第一个或第二个缓存中被找到,查询仍然可能根据 cypher.min_replan_intervalcypher.statistics_divergence_thresholdconf/neo4j.conf 参数进行重新规划。

cypher.min_replan_interval 用于定义缓存计划在重新规划之前存在的持续时间,默认为 10 秒。

cypher.statistics_divergence_threshold 用于指示 Cypher 使用的基础数据的统计信息发生了多少百分比的变化。默认值为 0.75,表示如果对象中的统计信息自上次生成缓存计划以来发生了超过 75% 的变化,则需要生成新的计划。例如,运行

// remove all :Person nodes
match (n:Person) detach delete n;
// create 10 :Person nodes
foreach (x in range (1,10) | create (n:Person {id:x}));
// list the 10 :Person nodes created
match (n:Person) return n.id order by n.id desc;
// create 8 new :Person nodes
foreach (x in range (11,18) | create (n:Person {id:x}));
// list the 18 :Person nodes
match (n:Person) return n.id order by n.id desc;

第二个 match (n:Person) return n.id order by n.id desc; 将分别被规划,特别是第二个实例,尽管具有相同的哈希值,但 :Person 的统计信息已从 10 个节点更改为 18 个节点,从而超过了 75% 的变化。

如果由于上述 2 个参数导致现有计划需要重新规划,则 logs/debug.log 将记录

2017-03-31 19:14:27.820+0000 INFO [o.n.c.i.ExecutionEngine] Discarded stale query from the query cache: match (n:Person)
return n.id order by n.id desc;
2017-03-31 19:14:27.821+0000 INFO [o.n.c.i.EnterpriseCompatibilityFactory] Discarded stale query from the query cache: match
(n:Person) return n.id order by n.id desc;

此外,应该注意的是,当从缓存中删除查询计划以腾出空间以供新计划使用时,会使用最不经常使用 (LFU) 算法。因此,如果添加到计划缓存中的第一个查询每 1 秒运行一次,而添加到查询计划缓存中的第二个查询每 2 分钟添加一次,那么当我们需要从缓存中删除查询计划以腾出空间用于新查询时,我们将删除第二个查询而不是第一个,因为第一个更常被调用。

最后应该注意的是,任何模式更改(例如索引/约束创建/删除)都会刷新整个查询计划缓存。