知识库

了解查询计划缓存

当 Cypher 语句首次提交时,Neo4j 会尝试在规划之前确定查询是否已在计划缓存中。默认情况下,Neo4j 会根据 conf/neo4j.conf 参数 dbms.query_cache_size 将 1000 个查询计划保存在缓存中。事实上这实际上代表了 2 个查询计划缓存。

  • 字符串缓存

当 Cypher 最初提交时,Cypher 语句会对其原始字符串进行哈希计算。使用得到的哈希值,我们会尝试判断该语句是否已经存在于计划缓存中,如果存在,则可能不需要重新规划。

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

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

另外,逻辑相同但空格/换行不同的语句也会产生不同的哈希值。以下三个语句会产生不同的哈希值,可能需要重新规划。

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

MATCH (n)
return COUNT(n);

PROFILE/EXPLAIN 为前缀的 Cypher 语句在计算哈希之前会先去掉 PROFILE/EXPLAIN。以下两个语句的哈希值相同。

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

如果在第一个缓存中未找到该 Cypher 语句的哈希值,Neo4j 将尝试在第二个缓存中查找。

  • AST 缓存

Neo4j 编译器会将查询字符串解析为抽象语法树(AST),即查询的对象表示。优化器随后会对语句进行规范化,以便更容易进行规划。例如

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 语句在第一个或第二个缓存中被找到,仍可能根据 conf/neo4j.conf 参数 cypher.min_replan_intervalcypher.statistics_divergence_threshold 进行重新规划。

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% 的变化。

如果由于上述两个参数导致需要重新规划已有计划,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) 算法。因此,如果第一个加入计划缓存的查询每秒运行一次,而第二个加入的查询每两分钟运行一次,当需要移除缓存中的某个查询计划以容纳新查询时,会先移除第二个查询,因为第一个查询的调用频率更高。

最后,需要说明的是,任何模式更改,例如索引/约束的创建或删除,都会清空整个查询计划缓存。

© . This site is unofficial and not affiliated with Neo4j, Inc.