查询数据库

一旦您已连接到数据库,即可使用 CypherDriver.execute_query() 方法运行查询。

写入数据库

要创建两个分别代表 AliceDavid 的节点,并在它们之间建立 KNOWS 关系,请使用 Cypher 子句 CREATE

创建两个节点和一个关系
summary = driver.execute_query(""" (1)
    CREATE (a:Person {name: $name})
    CREATE (b:Person {name: $friendName})
    CREATE (a)-[:KNOWS]->(b)
    """,
    name="Alice", friendName="David",  (2)
    database_="<database-name>",  (3)
).summary
print("Created {nodes_created} nodes in {time} ms.".format(
    nodes_created=summary.counters.nodes_created,
    time=summary.result_available_after
))
1 Cypher 查询语句
2 作为关键字参数的查询参数
3 运行查询的目标数据库

从数据库读取

要从数据库检索信息,请使用 Cypher 子句 MATCH

检索所有喜欢其他 PersonPerson 节点
records, summary, keys = driver.execute_query("""
    MATCH (p:Person)-[:KNOWS]->(:Person)
    RETURN p.name AS name
    """,
    database_="<database-name>",
)

# Loop through results and do something with them
for record in records:  (1)
    print(record.data())  # get record as dict

# Summary information  (2)
print("The query `{query}` returned {records_count} records in {time} ms.".format(
    query=summary.query, records_count=len(records),
    time=summary.result_available_after
))
1 records 包含作为 Record 对象数组的结果
2 summary 包含服务器返回的执行摘要

更新数据库

要更新数据库中实体的信息,请使用 Cypher 子句 MATCHSET

更新 Alice 节点以添加 age 属性
records, summary, keys = driver.execute_query("""
    MATCH (p:Person {name: $name})
    SET p.age = $age
    """, name="Alice", age=42,
    database_="<database-name>",
)
print(f"Query counters: {summary.counters}.")

要创建一个链接到两个现有节点的新关系,请结合使用 Cypher 子句 MATCHCREATE

AliceBob 之间创建 :KNOWS 关系
records, summary, keys = driver.execute_query("""
    MATCH (alice:Person {name: $name})  (1)
    MATCH (bob:Person {name: $friend})  (2)
    CREATE (alice)-[:KNOWS]->(bob)  (3)
    """, name="Alice", friend="Bob",
    database_="<database-name>",
)
print(f"Query counters: {summary.counters}.")
1 检索名为 Alice 的人物节点并将其绑定到变量 alice
2 检索名为 Bob 的人物节点并将其绑定到变量 bob
3 创建一个从 alice 绑定的节点出发,连接到名为 BobPerson 节点的新 :KNOWS 关系

从数据库删除

要删除节点及其所有关联关系,请使用 Cypher 子句 DETACH DELETE

移除 Alice 节点
# This does not delete _only_ p, but also all its relationships!
records, summary, keys = driver.execute_query("""
    MATCH (p:Person {name: $name})
    DETACH DELETE p
    """, name="Alice",
    database_="<database-name>",
)
print(f"Query counters: {summary.counters}.")

查询参数

切勿直接将参数硬编码或拼接进查询字符串中。相反,请始终使用占位符,并以 Cypher 参数的形式提供动态数据。这样做是为了

  1. 性能优势:Neo4j 会编译并缓存查询,但前提是查询结构保持不变;

  2. 出于安全考虑:请参阅防止 Cypher 注入

查询参数既可以作为多个关键字参数传递,也可以组合在字典中并传递给 parameters_ 关键字参数。如果两者混合使用,关键字参数的优先级高于字典中的参数。

以关键字参数形式传递查询参数
driver.execute_query(
    "MERGE (:Person {name: $name})",
    name="Alice", age=42,
    database_="<database-name>",
)
以字典形式传递查询参数
parameters = {
    "name": "Alice",
    "age": 42
}
driver.execute_query(
    "MERGE (:Person {name: $name})",
    parameters_=parameters,
    database_="<database-name>",
)

您的任何关键字查询参数均不得以单个下划线结尾。 这是为了避免与关键字配置参数发生冲突。如果您需要使用此类参数名称,请将它们放入 parameters_ 字典中。

在某些情况下,查询结构可能无法在所有部分使用参数。对于这些极少数用例,请参见属性键、关系类型和标签中的动态值

错误处理

查询运行可能会因多种原因失败,并引发不同的异常。使用 driver.execute_query() 时,如果失败被视为瞬态故障(例如由于服务器暂时不可用),驱动程序会自动重试运行失败的查询。

如果操作在配置的最大重试时间后仍然失败,则会引发异常。

所有来自服务器的异常都是 Neo4jError 的子类。您可以使用异常的代码来稳定地识别特定错误;错误消息并不是稳定的标记,不应依赖于它们。

基本错误处理
# from neo4j.exceptions import Neo4jError

try:
    driver.execute_query('MATCH (p:Person) RETURN', database_='<database-name>')
except Neo4jError as e:
    print('Neo4j error code:', e.code)
    print('Exception message:', e.message)
'''
Neo4j error code: Neo.ClientError.Statement.SyntaxError
Exception message: Invalid input '': expected an expression, '*', 'ALL' or 'DISTINCT' (line 1, column 24 (offset: 23))
"MATCH (p:Person) RETURN"
                        ^
'''

异常对象还将错误公开为 GQL 状态对象。Neo4j 错误代码GQL 错误代码的主要区别在于 GQL 代码更细粒度:单个 Neo4j 错误代码可能会被拆分为多个更具体的 GQL 错误代码。

触发异常的实际原因有时可以在可选的 GQL-status 对象 __cause__ 中找到,它本身也是一个异常(或 None)。在到达捕获异常的根本原因之前,您可能需要递归遍历原因链。在下面的示例中,异常的 GQL 状态代码为 42001,但错误的实际来源状态代码为 42I06

Neo4jError 与 GQL 相关方法的使用
# from neo4j.exceptions import Neo4jError

try:
    driver.execute_query('MATCH (p:Person) RETURN', database_='<database-name>')
except Neo4jError as e:
    print('Exception GQL status:', e.gql_status)
    print('Exception GQL status description:', e.gql_status_description)
    print('Exception GQL classification:', e.gql_classification)
    print('Exception GQL cause:', e.__cause__)
    print('Exception GQL diagnostic record:', e.diagnostic_record)
'''
Exception GQL status: 42001
Exception GQL status description: error: syntax error or access rule violation - invalid syntax
Exception GQL classification: GqlErrorClassification.CLIENT_ERROR
Exception GQL cause: {gql_status: 42I06} {gql_status_description: error: syntax error or access rule violation - invalid input. Invalid input '', expected: an expression, '*', 'ALL' or 'DISTINCT'.} {message: 42I06: Invalid input '', expected: an expression, '*', 'ALL' or 'DISTINCT'.} {diagnostic_record: {'_classification': 'CLIENT_ERROR', '_position': {'line': 1, 'column': 24, 'offset': 23}, 'OPERATION': '', 'OPERATION_CODE': '0', 'CURRENT_SCHEMA': '/'}} {raw_classification: CLIENT_ERROR}
Exception GQL diagnostic record: {'_classification': 'CLIENT_ERROR', '_position': {'line': 1, 'column': 24, 'offset': 23}, 'OPERATION': '', 'OPERATION_CODE': '0', 'CURRENT_SCHEMA': '/'}
'''

当您希望应用程序根据服务器抛出的确切错误采取不同行为时,GQL 状态码特别有用。

区分不同的错误代码
# from neo4j.exceptions import Neo4jError

try:
    driver.execute_query('MATCH (p:Person) RETURN', database_='<database-name>')
except Neo4jError as e:
    if e.find_by_gql_status('42001'):
        # Neo.ClientError.Statement.SyntaxError
        # special handling of syntax error in query
        print(e.message)
    elif e.find_by_gql_status('42NFF'):
        # Neo.ClientError.Security.Forbidden
        # special handling of user not having CREATE permissions
        print(e.message)
    else:
        # handling of all other exceptions
        print(e.message)

当异常没有 GQL-status 对象时,会返回 GQL 状态代码 50N42。如果驱动程序连接到旧版本的 Neo4j 服务器,可能会发生这种情况。请不要依赖此状态代码,因为未来的 Neo4j 服务器版本可能会将其更改为更合适的代码。

瞬态服务器错误可以在无需更改原始请求的情况下重试。您可以通过 Neo4jError.is_retryable() 方法判断错误是否为瞬态错误,该方法可深入了解后续尝试是否可能成功。这在显式事务中运行查询时特别有用,有助于确定失败的查询是否值得重新运行。

查询配置

您可以提供额外的关键字参数来更改 .execute_query() 的默认行为。配置参数以 _ 为后缀。

数据库选择

请始终使用 database_ 参数显式指定数据库,即使在单数据库实例中也是如此。这使得驱动程序能更高效地工作,因为它节省了向服务器解析主数据库所需的网络往返时间。如果没有指定数据库,则使用用户的默认数据库(home database)

driver.execute_query(
    "MATCH (p:Person) RETURN p.name",
    database_="<database-name>",
)
建议通过配置方法指定数据库,而不是使用 USE Cypher 子句。如果服务器在集群上运行,带有 USE 的查询需要启用 服务器端路由。查询的执行时间也可能更长,因为它们第一次尝试时可能无法到达正确的集群成员,需要被路由到包含所请求数据库的集群成员。

请求路由

在集群环境中,默认情况下所有查询都会被引导至领导者(leader)节点。为了提高读取查询的性能,您可以使用参数 routing_="r" 将查询路由到只读(read)节点。

driver.execute_query(
    "MATCH (p:Person) RETURN p.name",
    routing_="r",  # short for neo4j.RoutingControl.READ
    database_="<database-name>",
)

尽管在读取模式下执行写入查询会导致运行时错误,但您不应依赖此功能进行访问控制。这两种模式的区别在于:读取事务将被路由到集群中的任何节点,而写入事务会被定向到主节点(primaries)。不能保证以读取模式提交的写入查询一定会遭到拒绝。

以其他用户身份运行查询

您可以使用 auth_ 参数通过其他用户执行查询。在查询级别切换用户比创建新的 Driver 对象成本更低。随后,查询将在给定用户的安全上下文(例如:默认数据库、权限等)中运行。

driver.execute_query(
    "MATCH (p:Person) RETURN p.name",
    auth_=("<username>", "<password>"),
    database_="<database-name>",
)

impersonated_user_ 参数提供了类似的功能。不同之处在于,您无需知道用户的密码即可模拟该用户,但创建 Driver 时所使用的用户需要具备相应的权限

driver.execute_query(
    "MATCH (p:Person) RETURN p.name",
    impersonated_user_="<username>",
    database_="<database-name>",
)

转换查询结果

您可以使用 result_transformer_ 参数将查询结果转换为不同的数据结构。驱动程序提供了内置方法将结果转换为 pandas 数据帧或图形,但您也可以自行编写转换器。

有关更多信息,请参阅操作查询结果

完整示例

from neo4j import GraphDatabase
from neo4j.exceptions import Neo4jError


URI = "<database-uri>"
AUTH = ("<username>", "<password>")

people = [{"name": "Alice", "age": 42, "friends": ["Bob", "Peter", "Anna"]},
          {"name": "Bob", "age": 19},
          {"name": "Peter", "age": 50},
          {"name": "Anna", "age": 30}]

with GraphDatabase.driver(URI, auth=AUTH) as driver:
    try:
        # Create some nodes
        for person in people:
            records, summary, keys = driver.execute_query(
                "MERGE (p:Person {name: $person.name, age: $person.age})",
                person=person,
                database_="<database-name>",
            )

        # Create some relationships
        for person in people:
            if person.get("friends"):
                records, summary, keys = driver.execute_query("""
                    MATCH (p:Person {name: $person.name})
                    UNWIND $person.friends AS friend_name
                    MATCH (friend:Person {name: friend_name})
                    MERGE (p)-[:KNOWS]->(friend)
                    """, person=person,
                    database_="<database-name>",
                )

        # Retrieve Alice's friends who are under 40
        records, summary, keys = driver.execute_query("""
            MATCH (p:Person {name: $name})-[:KNOWS]-(friend:Person)
            WHERE friend.age < $age
            RETURN friend
            """, name="Alice", age=40,
            routing_="r",
            database_="<database-name>",
        )
        # Loop through results and do something with them
        for record in records:
            print(record)
        # Summary information
        print("The query `{query}` returned {records_count} records in {time} ms.".format(
            query=summary.query, records_count=len(records),
            time=summary.result_available_after
        ))

    except Neo4jError as e:
        print(e)
        # further logging/processing

有关更多信息,请参阅 API 文档 → Driver.execute_query()

术语表

LTS (长期支持版)

长期支持 (Long Term Support) 版本是保证在若干年内得到支持的版本。Neo4j 4.4 和 5.26 是 LTS 版本。

Aura

Aura 是 Neo4j 的全托管云服务。它提供免费和付费计划。

Cypher

Cypher 是 Neo4j 的图查询语言,允许您从数据库中检索数据。它就像 SQL,但专用于图数据库。

APOC

Awesome Procedures On Cypher (APOC) 是一个包含(许多)函数的库,这些函数在 Cypher 本身中难以轻松实现。

Bolt

Bolt 是用于 Neo4j 实例和驱动程序之间交互的协议。默认监听 7687 端口。

ACID

原子性 (Atomicity)、一致性 (Consistency)、隔离性 (Isolation)、持久性 (Durability) (ACID) 是保证数据库事务可靠处理的属性。符合 ACID 的 DBMS 确保即使发生故障,数据库中的数据也能保持准确和一致。

最终一致性

如果一个数据库能保证所有集群成员在某个时间点都存储了数据的最新版本,则该数据库具有最终一致性。

因果一致性

如果读写查询被集群中的每个成员以相同的顺序看到,则数据库具有因果一致性。这比最终一致性更强。

NULL

空标记不是一种类型,而是缺失值的占位符。更多信息,请参阅 Cypher → 使用 null

事务

事务是一个工作单元,要么被提交,要么在失败时被回滚。例如银行转账:它涉及多个步骤,但它们必须全部成功或全部撤销,以避免钱从一个账户扣除却未存入另一个账户的情况。

背压

背压是对数据流的抵抗力。它确保客户端不会被过快发送的数据压垮,从而超出其处理能力。

书签

书签是代表数据库某种状态的标记。通过将一个或多个书签与查询一起传递,服务器将确保在所表示的状态建立之前,该查询不会被执行。

事务函数

事务函数是由 execute_readexecute_write 调用执行的回调。如果发生服务器故障,驱动程序会自动重新执行该回调。

驱动程序 (Driver)

一个 Driver 对象保存了与 Neo4j 数据库建立连接所需的详细信息。