探索查询执行摘要

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

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

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

  • 通知 — 查询执行期间服务器产生的额外信息

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

检索执行摘要

使用 IDriver.ExecutableQuery() 运行查询时,执行摘要是默认返回对象的一部分,可通过 Summary 属性获取。

var result = await driver.ExecutableQuery(@"
        UNWIND ['Alice', 'Bob'] AS name
        MERGE (p:Person {name: name})
    ")
    .WithConfig(new QueryConfig(database: "<database-name>"))
    .ExecuteAsync();
var resultSummary = result.Summary;

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

using var session = driver.AsyncSession(SessionConfigBuilder.ForDatabase("<database-name>"));
var resultSummary = await session.ExecuteWriteAsync(
    async tx => {
        var result = await tx.RunAsync(@"
            UNWIND ['Alice', 'Bob'] AS name
            MERGE (p:Person {name: name})
        ");
        return await result.ConsumeAsync();
    }
);

查询计数器

属性 IResultSummary.Counters 包含查询触发的操作计数(以 ICounters 对象的形式)。

插入一些数据并显示查询计数器
var result = await driver.ExecutableQuery(@"
        MERGE (p:Person {name: $name})
        MERGE (p)-[:KNOWS]->(:Person {name: $friend})
    ")
    .WithParameters(new { name = "Mark", friend = "Bob" })
    .WithConfig(new QueryConfig(database: "<database-name>"))
    .ExecuteAsync();
var queryCounters = result.Summary.Counters;
Console.WriteLine(queryCounters);
/*
Counters{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,服务器会返回它将会用来执行查询的计划,但并不实际执行查询。该计划以 IPlan 对象的形式存放在属性 IResultSummary.Plan 中,包含用于获取结果集的 Cypher 操作符 列表。利用这些信息可以定位潜在瓶颈或寻找性能改进的空间(例如通过创建索引)。

var result = await driver.ExecutableQuery("EXPLAIN MATCH (p {name: $name}) RETURN p")
    .WithParameters(new { name = "Alice" })
    .WithConfig(new QueryConfig(database: "<database-name>"))
    .ExecuteAsync();
var queryPlan = result.Summary.Plan;
Console.WriteLine(queryPlan.Arguments["string-representation"]);
/*
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,服务器会返回它用于执行该查询的执行计划以及分析统计信息。内容包括使用的操作符列表以及每个中间步骤的附加分析信息。该计划以 IProfiledPlan 对象的形式存放在属性 IResultSummary.Profile 中。请注意,查询同时被执行,因此结果对象还包含所有结果记录。

var result = await driver.ExecutableQuery("PROFILE MATCH (p {name: $name}) RETURN p")
    .WithParameters(new { name = "Alice" })
    .WithConfig(new QueryConfig(database: "<database-name>"))
    .ExecuteAsync();
// Note: `result.Result` is non-empty
var queryPlan = result.Summary.Profile;
Console.WriteLine(queryPlan.Arguments["string-representation"]);
/*
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。无界最短路径会触发性能通知

属性 IResultSummary.GqlStatusObjects 包含一个符合 GQL 标准的状态对象列表(类型为 IGqlStatusObject)。

这些对象包含通知或查询的结果状态(00000 表示“成功”,02000 表示“无数据”,00001 表示“省略结果”)。该列表始终至少包含一项,即结果状态。

var result = await driver.ExecutableQuery(@"
        MATCH p=shortestPath((:Person {name: $start})-[*]->(:Person {name: $end}))
        RETURN p
    ")
    .WithParameters(new { start = "Alice", end = "Bob" })
    .WithConfig(new QueryConfig(database: "<database-name>"))
    .ExecuteAsync();
var notifications = result.Summary.GqlStatusObjects;
foreach (var n in notifications) {
    Console.WriteLine(n);
}
/*
GqlStatusObject {
    Position = , RawClassification = , RawSeverity = , Title = , IsNotification = False, GqlStatus = 00000, StatusDescription = note: successful completion, Classification = Unknown, Severity = Unknown, DiagnosticRecord = System.Collections.Generic.Dictionary`2[System.String,System.Object], RawDiagnosticRecord = [{CURRENT_SCHEMA, /}, {OPERATION, }, {OPERATION_CODE, 0}]
}
GqlStatusObject {
    Position = InputPosition{Offset=30, Line=2, Column=30},
    RawClassification = PERFORMANCE,
    RawSeverity = INFORMATION,
    Title = The provided pattern is unbounded, consider adding an upper limit to the number of node hops.,
    IsNotification = True,
    GqlStatus = 03N91,
    StatusDescription = info: unbounded variable length pattern. The provided pattern '(:Person {name: $start})-[*]->(:Person {name: $end})' is unbounded. Shortest path with an unbounded pattern may result in long execution times. Use an upper limit (e.g. '[*..5]') on the number of node hops in your pattern.,
    Classification = Performance,
    Severity = Information,
    DiagnosticRecord = System.Collections.Generic.Dictionary`2[System.String,System.Object],
    RawDiagnosticRecord = [{CURRENT_SCHEMA, /}, {OPERATION, }, {OPERATION_CODE, 0}, {_classification, PERFORMANCE}, {_severity, INFORMATION}, {_position, [{offset, 30}, {line, 2}, {column, 30}]}]
}
*/

在 6.0 中已弃用

属性 IResultSummary.Notifications 包含一个 INotification 对象的列表。

var result = await driver.ExecutableQuery(@"
        MATCH p=shortestPath((:Person {name: $start})-[*]->(:Person {name: $end}))
        RETURN p
    ")
    .WithParameters(new { start = "Alice", end = "Bob" })
    .WithConfig(new QueryConfig(database: "<database-name>"))
    .ExecuteAsync();
var notifications = result.Summary.Notifications;
foreach (var n in notifications) {
    Console.WriteLine(n);
}
/*
Notification{
    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.
    Position=InputPosition{Offset=30, Line=2, Column=30}
    SeverityLevel=Information
    Category=Performance
    RawSeverityLevel=INFORMATION
    RawCategory=PERFORMANCE
}
*/

过滤通知

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

.WithNotifications() 接受以下两种形式之一:SeverityClassification 列表(Classification),或 SeverityCategory 列表(已弃用)。所提供的分类/类别将被排除在输出之外。
严重程度过滤器适用于 Neo4j 和 GQL 通知。
分类过滤器同时作用于类别和分类。
要禁用所有通知,请使用 .WithNotificationsDisabled()

您可以在创建新的 IDriver 对象时以及创建会话时设置通知的相关参数。

仅允许 Warning 通知,但不包括分类/类别为 HintGeneric 的通知
// at driver level
await using var driver = GraphDatabase.Driver(
    dbUri, AuthTokens.Basic(dbUser, dbPassword), conf =>
    conf.WithNotifications(
        Severity.Warning,
        disabledClassifications: [Classification.Hint, Classification.Generic])
);

// at session level
using var session = driver.AsyncSession(conf => conf
    .WithDatabase("<database-name>")
    .WithNotifications(
        Severity.Warning,
        disabledClassifications: [Classification.Hint, Classification.Generic])
);
禁用所有通知
// at driver level
await using var driver = GraphDatabase.Driver(
    dbUri, AuthTokens.Basic(dbUser, dbPassword),
    conf => conf.WithNotificationsDisabled()
);

// at session level
using var session = driver.AsyncSession(conf => conf
    .WithDatabase("<database-name>")
    .WithNotificationsDisabled()
);