探索查询执行摘要

在查询结果的所有记录都处理完毕后,服务器通过返回执行摘要来结束事务。它以一个 ResultSummary 对象的形式返回,并包含以下信息:

  • 查询计数器 — 查询在服务器上触发的更改

  • 查询执行计划 — 数据库将如何(或已经)执行查询

  • 通知 — 查询运行期间服务器发出的额外信息

  • 时间信息和查询请求摘要

检索执行摘要

使用 Driver.executableQuery() 运行查询时,执行摘要是默认返回对象的一部分,可通过 .summary() 方法获取。

var result = driver.executableQuery("""
    UNWIND ['Alice', 'Bob'] AS name
    MERGE (p:Person {name: name})
    """)
    .withConfig(QueryConfig.builder().withDatabase("<database-name>").build())
    .execute();
var resultSummary = result.summary();

如果您使用 事务函数,可以通过 Result.consume() 方法获取查询执行摘要。注意:一旦请求执行摘要,结果流将被 耗尽,此时尚未处理的记录将不再可用。

try (var session = driver.session(SessionConfig.builder().withDatabase("<database-name>").build())) {
    var resultSummary = session.executeWrite(tx -> {
        var result = tx.run("""
        UNWIND ['Alice', 'Bob'] AS name
        MERGE (p:Person {name: name})
        """);
        return result.consume();
    });
}

查询计数器

方法 ResultSummary.counters() 返回查询触发的各类操作计数(以 SummaryCounters 对象呈现)。

插入一些数据并显示查询计数器
var result = driver.executableQuery("""
    MERGE (p:Person {name: $name})
    MERGE (p)-[:KNOWS]->(:Person {name: $friend})
    """).withParameters(Map.of("name", "Mark", "friend", "Bob"))
    .withConfig(QueryConfig.builder().withDatabase("<database-name>").build())
    .execute();
var queryCounters = result.summary().counters();
System.out.println(queryCounters);

/*
InternalSummaryCounters{nodesCreated=2, nodesDeleted=0, relationshipsCreated=1, relationshipsDeleted=0,
propertiesSet=2, labelsAdded=2, labelsRemoved=0, indexesAdded=0, indexesRemoved=0, constraintsAdded=0,
constraintsRemoved=0, systemUpdates=0}
*/

另外还有两个布尔方法充当元计数器

  • .containsUpdates() — 查询是否在所运行的数据库上触发了任意写操作

  • .containsSystemUpdates() — 查询是否在 system 数据库上触发了写操作

查询执行计划

如果在查询前加上 EXPLAIN,服务器会返回它本将用于执行查询的计划,但实际并不执行查询。该计划可通过 ResultSummary.plan() 方法作为一个 Plan 对象获取,并包含用于检索结果集的 Cypher 运算符 列表。可利用这些信息定位瓶颈或寻找 性能改进 的潜在空间(例如创建索引)。

var result = driver.executableQuery("EXPLAIN MATCH (p {name: $name}) RETURN p")
    .withParameters(Map.of("name", "Alice"))
    .withConfig(QueryConfig.builder().withDatabase("<database-name>").build())
    .execute();
var queryPlan = result.summary().plan().arguments().get("string-representation");
System.out.println(queryPlan);

/*
Planner COST
Runtime PIPELINED
Runtime version 5.0
Batch size 128

+-----------------+----------------+----------------+---------------------+
| Operator        | Details        | Estimated Rows | Pipeline            |
+-----------------+----------------+----------------+---------------------+
| +ProduceResults | p              |              1 |                     |
| |               +----------------+----------------+                     |
| +Filter         | p.name = $name |              1 |                     |
| |               +----------------+----------------+                     |
| +AllNodesScan   | p              |             10 | Fused in Pipeline 0 |
+-----------------+----------------+----------------+---------------------+

Total database accesses: ?
*/

如果在查询前使用关键字 PROFILE,服务器会返回实际运行查询时使用的执行计划及分析统计信息。这包括实际使用的运算符列表以及每个中间步骤的额外分析数据。计划通过 ResultSummary.profile() 方法作为 Plan 对象获取。请注意,查询会被实际 执行,因此结果对象仍包含查询结果记录。

var result = driver.executableQuery("PROFILE MATCH (p {name: $name}) RETURN p")
    .withParameters(Map.of("name", "Alice"))
    .withConfig(QueryConfig.builder().withDatabase("<database-name>").build())
    .execute();
var queryPlan = result.summary().profile().arguments().get("string-representation");
System.out.println(queryPlan);

/*
Planner COST
Runtime PIPELINED
Runtime version 5.0
Batch size 128

+-----------------+----------------+----------------+------+---------+----------------+------------------------+-----------+---------------------+
| Operator        | Details        | Estimated Rows | Rows | DB Hits | Memory (Bytes) | Page Cache Hits/Misses | Time (ms) | Pipeline            |
+-----------------+----------------+----------------+------+---------+----------------+------------------------+-----------+---------------------+
| +ProduceResults | p              |              1 |    1 |       3 |                |                        |           |                     |
| |               +----------------+----------------+------+---------+----------------+                        |           |                     |
| +Filter         | p.name = $name |              1 |    1 |       4 |                |                        |           |                     |
| |               +----------------+----------------+------+---------+----------------+                        |           |                     |
| +AllNodesScan   | p              |             10 |    4 |       5 |            120 |                 9160/0 |   108.923 | Fused in Pipeline 0 |
+-----------------+----------------+----------------+------+---------+----------------+------------------------+-----------+---------------------+

Total database accesses: 12, total allocated memory: 184
*/

有关更多信息和示例,请参见 基本查询调优

通知

执行查询后,服务器可以在查询结果旁返回通知。通知包括性能改进建议、关于使用已弃用特性的警告以及其他关于 Neo4j 使用不佳的提示。

示例 1。无界最短路径会触发性能通知

方法 ResultSummary.gqlStatusObjects() 返回一个有序的 GQL 兼容状态对象集合。

该集合可以同时包含 Notification 对象和 GqlStatusObject 对象。后者表示查询的结果状态:00000 表示“成功”,02000 表示“无数据”,00001 表示“省略结果”。集合始终至少包含一个条目,即结果状态条目。

var result = driver.executableQuery("""
    MATCH p=shortestPath((:Person {name: $start})-[*]->(:Person {name: $end}))
    RETURN p
    """)
    .withParameters(Map.of("start", "Alice", "end", "Bob"))
    .withConfig(QueryConfig.builder().withDatabase("<database-name>").build())
    .execute();
var statuses = result.summary().gqlStatusObjects();
System.out.println(statuses);
/*
[
    InternalGqlStatusObject{gqlStatus='02000', statusDescription='note: no data', diagnosticRecord={OPERATION_CODE="0", OPERATION="", CURRENT_SCHEMA="/"}},
    code=Neo.ClientNotification.Statement.UnboundedVariableLengthPattern, title=The provided pattern is unbounded, consider adding an upper limit to the number of node hops., description=Using shortest path with an unbounded pattern will likely result in long execution times. It is recommended to use an upper limit to the number of node hops in your pattern., severityLevel=InternalNotificationSeverity[type=INFORMATION, level=800], rawSeverityLevel=INFORMATION, classification=PERFORMANCE, rawClassification=PERFORMANCE, position={offset=21, line=1, column=22}
]
*/

在 6.0 中已弃用

方法 ResultSummary.notifications() 返回一个 Notification 对象列表。

var result = driver.executableQuery("""
    MATCH p=shortestPath((:Person {name: $start})-[*]->(:Person {name: $end}))
    RETURN p
    """)
    .withParameters(Map.of("start", "Alice", "end", "Bob"))
    .withConfig(QueryConfig.builder().withDatabase("<database-name>").build())
    .execute();
var notifications = result.summary().notifications();
System.out.println(notifications);
/*
[
    code=Neo.ClientNotification.Statement.UnboundedVariableLengthPattern,
    title=The provided pattern is unbounded, consider adding an upper limit to the number of node hops.,
    description=Using shortest path with an unbounded pattern will likely result in long execution times. It is recommended to use an upper limit to the number of node hops in your pattern.,
    severityLevel=InternalNotificationSeverity[type=INFORMATION,
    level=800],
    rawSeverityLevel=INFORMATION,
    category=InternalNotificationCategory[type=PERFORMANCE],
    rawCategory=PERFORMANCE,
    position={offset=21, line=1, column=22}
]
*/

过滤通知

默认情况下,服务器会针对每个查询分析所有类别和严重程度的通知。可使用配置方法 .withMinimumNotificationSeverity().withDisabledNotificationClassification() 来调整感兴趣的通知的严重程度和/或类别/分类,或完全禁用它们。限制服务器可产生的通知数量可以略微提升性能。

严重程度过滤同时适用于 Neo4j 和 GQL 通知。分类过滤则同时作用于类别和分类。

这些方法既可以在创建 Driver 实例时的 Config 对象上调用,也可以在创建会话时的 SessionConfig 对象上调用。

只允许 WARNING 通知,且不接受 HINTGENERIC 分类的通知
// import java.util.Set
// import org.neo4j.driver.Config;
// import org.neo4j.driver.NotificationClassification;
// import org.neo4j.driver.NotificationConfig;
// import org.neo4j.driver.NotificationSeverity;
// import org.neo4j.driver.SessionConfig;

// at `Driver` level
var driver = GraphDatabase.driver(
    dbUri, AuthTokens.basic(dbUser, dbPassword),
    Config.builder()
    .withMinimumNotificationSeverity(NotificationSeverity.WARNING)  // NotificationSeverity.OFF to disable entirely
    .withDisabledNotificationClassifications(Set.of(NotificationClassification.HINT, NotificationClassification.GENERIC))  // filters categories as well
    .build()
);

// at `Session` level
var session = driver.session(
    SessionConfig.builder()
    .withDatabase("<database-name>")
    .withMinimumNotificationSeverity(NotificationSeverity.WARNING)  // NotificationSeverity.OFF to disable entirely
    .withDisabledNotificationClassifications(Set.of(NotificationClassification.HINT, NotificationClassification.GENERIC))  // filters categories as well
    .build()
);
禁用所有通知
// import org.neo4j.driver.Config;
// import org.neo4j.driver.NotificationSeverity;
// import org.neo4j.driver.SessionConfig;

// at `Driver` level
var driver = GraphDatabase.driver(
    dbUri, AuthTokens.basic(dbUser, dbPassword),
    Config.builder()
    .withMinimumNotificationSeverity(NotificationSeverity.OFF)
    .build()
);

// at `Session` level
var session = driver.session(
    SessionConfig.builder()
    .withDatabase("<database-name>")
    .withMinimumNotificationSeverity(NotificationSeverity.OFF)
    .build()
);