探索查询执行摘要

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

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

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

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

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

检索执行摘要

使用 Driver.execute_query() 运行查询时,执行摘要是默认返回值的一部分,作为第二个对象。

records, result_summary, keys = driver.execute_query("""
    UNWIND ["Alice", "Bob"] AS name
    MERGE (p:Person {name: name})
    """, database_="<database-name>",
)
# or result_summary = driver.execute_query('<QUERY>').summary

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

def create_people(tx):
    result = tx.run("""
        UNWIND ["Alice", "Bob"] AS name
        MERGE (p:Person {name: name})
    """)
    return result.consume()

with driver.session(database="<database-name>") as session:
    result_summary = session.execute_write(create_people)

查询计数器

属性 ResultSummary.counters 包含查询触发的操作计数(作为 SummaryCounters 对象)。

插入一些数据并显示查询计数器
summary = driver.execute_query("""
    MERGE (p:Person {name: $name})
    MERGE (f:Person {name: $friend})
    MERGE (p)-[:KNOWS]->(f)
    """, name="Mark", friend="Bob",
    database_="<database-name>",
).summary
print(summary.counters)
"""
{'_contains_updates': True, 'labels_added': 2, 'relationships_created': 1,
 'nodes_created': 2, 'properties_set': 2}
"""

另外两个布尔属性充当元计数器

  • contains_updates — 查询是否在其运行的数据库上触发了任何写操作

  • contains_system_updates — 查询是否在 system 数据库上触发了任何写操作

查询执行计划

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

_, summary, _ = driver.execute_query("EXPLAIN MATCH (p {name: $name}) RETURN p", name="Alice")
print(summary.plan['args']['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,服务器会返回实际运行查询时使用的执行计划,并附带分析统计信息。它包括使用的操作符列表以及每个中间步骤的额外分析信息。计划可通过属性 ResultSummary.profile 获取。请注意,查询实际上已执行,因此结果对象仍然包含所有结果记录。

records, summary, _ = driver.execute_query("PROFILE MATCH (p {name: $name}) RETURN p", name="Alice")
print(summary.profile['args']['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。无界最短路径会触发性能通知

属性 ResultSummary.gql_status_objects 包含一系列 GqlStatusObject。这些是符合 GQL 标准的状态对象。

部分(但并非全部)GqlStatusObjects 为通知,而有些则报告结果状态:00000 表示“成功”,02000 表示“无数据”,00001 表示“省略结果”。ResultSummary.gql_status_objects 始终至少包含一条记录,即结果状态。

records, summary, keys = driver.execute_query("""
    MATCH p=shortestPath((:Person {name: 'Alice'})-[*]->(:Person {name: 'Bob'}))
    RETURN p
    """, database_="<database-name>"
)
for status in summary.gql_status_objects:
    print("GQLSTATUS:", status.gql_status)
    print("Description:", status.status_description)
    # Not all statuses are notifications.
    print("Is notification:", status.is_notification)

    # Notification and thus vendor-specific fields.
    # These fields are only meaningful for notifications.
    if status.is_notification:
        # The position in the query that caused the notification.
        print("Position:", status.position)

        # The notification's classification is counterpart to `neo4j.NotificationCategory`.
        # However, the term `category` has a different meaning in the context of GQL.
        print("Classification:", status.classification)
        print("Unparsed classification:", status.raw_classification)

        print("Severity:", status.severity)
        print("Unparsed severity:", status.raw_severity)

    # Any raw extra information provided by the DBMS:
    print("Diagnostic record:", status.diagnostic_record)
    print("=" * 80)
"""
GQLSTATUS: 02000
Description: note: no data
Is notification: False
Diagnostic record: {'OPERATION': '', 'OPERATION_CODE': '0', 'CURRENT_SCHEMA': '/'}
================================================================================
GQLSTATUS: 03N91
Description: info: unbounded variable length pattern. The provided pattern `(:Person {name: 'Alice'})-[*]->(:Person {name: 'Bob'})` 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.
Is notification: True
Position: line: 1, column: 22, offset: 21
Classification: NotificationClassification.PERFORMANCE
Unparsed classification: PERFORMANCE
Severity: NotificationSeverity.INFORMATION
Unparsed severity: INFORMATION
Diagnostic record: {'_classification': 'PERFORMANCE', '_status_parameters': {'pat': "(:Person {name: 'Alice'})-[*]->(:Person {name: 'Bob'})"}, '_severity': 'INFORMATION', '_position': {'offset': 21, 'line': 1, 'column': 22}, 'OPERATION': '', 'OPERATION_CODE': '0', 'CURRENT_SCHEMA': '/'}
================================================================================
"""

在 6.0 中已弃用

属性 ResultSummary.summary_notifications 包含一个 SummaryNotification 对象列表。

records, summary, keys = driver.execute_query("""
    MATCH p=shortestPath((:Person {name: 'Alice'})-[*]->(:Person {name: 'Bob'}))
    RETURN p
    """, database_="<database-name>"
)
"""
[SummaryNotification(
    title='The provided pattern is unbounded, consider adding an upper limit to the number of node hops.',
    code='Neo.ClientNotification.Statement.UnboundedVariableLengthPattern',
    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.',
    severity_level=<NotificationSeverity.INFORMATION: 'INFORMATION'>,
    category=<NotificationCategory.PERFORMANCE: 'PERFORMANCE'>,
    raw_severity_level='INFORMATION',
    raw_category='PERFORMANCE',
    position=SummaryNotificationPosition(line=1, column=22, offset=21)
)]
"""

过滤通知

默认情况下,服务器会对每个查询的所有通知类别和严重程度进行分析。使用参数 notifications_min_severity 和/或 notifications_disabled_categories/notifications_disabled_classifications 来限制你感兴趣的通知的严重程度和/或类别/分类。限制服务器可发出的通知数量会略微提升性能。你可以在创建 Driver 实例或创建会话时使用这些参数中的任意一个。

严重程度过滤器适用于 Neo4j 和 GQL 通知。类别和分类过滤器之所以分开,仅是因为 GQL 与 Neo4j 术语的差异;不过这两种过滤器都会影响所有形式的通知,因此应仅使用其中一种。如果同时提供了类别和分类过滤器,它们的内容会合并。从 6.0 版本起,notifications_disabled_categories 已被弃用。

通过将最小严重程度设置为 'OFF' 可以完全禁用通知。

仅允许 WARNING 通知,但不包括 HINTGENERIC 类别的通知
# at driver level
driver = neo4j.GraphDatabase.driver(
    URI, auth=AUTH,
    notifications_min_severity='WARNING',  # or 'OFF' to disable entirely
    notifications_disabled_classifications=['HINT', 'GENERIC'],  # filters categories as well
)

# at session level
session = driver.session(
    database="<database-name>",
    notifications_min_severity='INFORMATION',  # or 'OFF' to disable entirely
    notifications_disabled_classifications=['HINT']  # filters categories as well
)