更进一步的查询机制

隐式(或自动提交)事务

这是运行 Cypher 查询最基本和最有限的形式。驱动程序不会自动重试隐式事务,而是会对使用ExecuteQuery()托管事务运行的查询执行此操作。仅当其他驱动程序查询接口不适合目的或用于快速原型设计时,才应使用隐式事务。

您可以使用 SessionWithContext.Run() 方法运行隐式事务。它返回一个 ResultWithContext 对象,需要相应地处理

session := driver.NewSession(ctx, neo4j.SessionConfig{DatabaseName: "neo4j"})
defer session.Close(ctx)
result, err := session.Run(
    ctx,
    "CREATE (p:Person {name: $name}) RETURN p",
    map[string]any{
        "name": "Lucia",
    })

隐式事务在会话被销毁时或在同一会话中执行另一个事务之前最晚提交。除此之外,无法明确保证在会话的生命周期中隐式事务何时确切提交。要确保隐式事务已提交,可以在其结果上调用 .Consume(ctx) 方法。

由于驱动程序无法确定 SessionWithContext.Run() 调用中的查询是否需要数据库的读或写会话,因此它默认为写。如果您的隐式事务仅包含读取查询,则可以通过在创建会话时设置会话配置 AccessMode: neo4j.AccessModeRead使驱动程序感知,从而获得性能提升。

隐式事务是唯一可用于CALL { …​ } IN TRANSACTIONS 查询的事务。.

导入 CSV 文件

使用 SessionWithContext.Run() 最常见的用例是使用LOAD CSV Cypher 子句将大型 CSV 文件导入数据库,并防止由于事务大小导致的超时错误。

将 CSV 数据导入 Neo4j 数据库
session := driver.NewSession(ctx, neo4j.SessionConfig{DatabaseName: "neo4j"})
defer session.Close(ctx)
result, err := session.Run(
    ctx, `
    LOAD CSV FROM 'https://data.neo4j.com/bands/artists.csv' AS line
    CALL {
        WITH line
        MERGE (:Artist {name: line[1], age: toInteger(line[2])})
    } IN TRANSACTIONS OF 2 ROWS
    `, nil)
summary, _ := result.Consume(ctx)
fmt.Println("Query updated the database?",
    summary.Counters().ContainsUpdates())
虽然 LOAD CSV 可能会带来便利,但将 CSV 文件的解析延迟到您的 Go 应用程序并避免 LOAD CSV 并没有什么错误。实际上,将解析逻辑移动到应用程序可以使您更好地控制导入过程。有关高效的批量数据插入,请参见性能 → 批量数据创建

有关更多信息,请参见Cypher → 子句 → 加载 CSV

事务配置

通过在 SessionWithContext.Run() 调用的第三个参数之后提供配置回调,您可以对隐式事务进行进一步控制。配置回调允许指定查询超时并将元数据附加到事务。有关更多信息,请参见事务 - 事务配置

session := driver.NewSession(ctx, neo4j.SessionConfig{DatabaseName: "neo4j"})
defer session.Close(ctx)
people, err := session.Run(ctx,
    "MATCH (:Person) RETURN count(*) AS n",
    nil,
    neo4j.WithTxTimeout(5*time.Second),  // remember to import `time`
    neo4j.WithTxMetadata(map[string]any{"appName": "peopleTracker"}))

属性键、关系类型和标签中的动态值

通常,您不应该将参数直接连接到查询中,而应使用查询参数。但是,在某些情况下,您的查询结构会阻止在所有部分中使用参数。实际上,尽管参数可用于文字和表达式以及节点和关系 ID,但它们不能用于以下结构

  • 属性键,因此 MATCH (n) WHERE n.$param = 'something' 无效;

  • 关系类型,因此 MATCH (n)-[:$param]→(m) 无效;

  • 标签,因此 MATCH (n:$param) 无效。

对于这些查询,您被迫使用字符串连接。为了防止Cypher 注入,您应该将动态值括在反引号中并自行对其进行转义。请注意,Cypher 处理 Unicode,因此也要注意 Unicode 文字 \u0060

手动转义连接前的动态标签
dangerousLabel := "Person\\u0060n"
// convert \u0060 to literal backtick and then escape backticks
// remember to import `strings`
escapedLabel := strings.ReplaceAll(dangerousLabel, "\\u0060", "`")
escapedLabel = strings.ReplaceAll(escapedLabel, "`", "``")

result, err := neo4j.ExecuteQuery(ctx, driver,
    "MATCH (p:`" + escapedLabel + "` WHERE p.name = $name) RETURN p.name",
    map[string]any{
        "name": "Alice",
    },
    neo4j.EagerResultTransformer,
    neo4j.ExecuteQueryWithDatabase("neo4j"))

另一种避免字符串连接的解决方法是使用APOC 过程apoc.merge.node。它支持动态标签和属性键,但仅支持节点合并。

使用 apoc.merge.node 创建具有动态标签/属性键的节点
propertyKey := "name"
label := "Person"

result, err := neo4j.ExecuteQuery(ctx, driver,
    "CALL apoc.merge.node($labels, $properties)",
    map[string]any{
        "labels": []string{label},
        "properties": map[string]any{propertyKey: "Alice"},
    },
    neo4j.EagerResultTransformer,
    neo4j.ExecuteQueryWithDatabase("neo4j"))
如果在 Docker 中运行 Neo4j,则在启动容器时需要启用 APOC。请参见APOC → 安装 → Docker

日志记录

驱动程序将日志记录划分为驱动程序事件和 Bolt 事件。要启用驱动程序日志记录,请在实例化驱动程序时使用 Config.Log 选项

// import "github.com/neo4j/neo4j-go-driver/v5/neo4j/config"

driver, err := neo4j.NewDriverWithContext(
    dbUri,
    neo4j.BasicAuth(dbUser, dbPassword, ""),
    func(conf *config.Config) {
        conf.Log = neo4j.ConsoleLogger(neo4j.DEBUG)
    })
驱动程序连接时的日志输出示例
2023-07-03 08:07:19.316   INFO  [pool 1] Created
2023-07-03 08:07:19.316   INFO  [router 1] Created {context: map[address:localhost:7687]}
2023-07-03 08:07:19.316   INFO  [driver 1] Created { target: localhost:7687 }
2023-07-03 08:07:19.316  DEBUG  [session 2] Created
2023-07-03 08:07:19.316   INFO  [router 1] Reading routing table from initial router: localhost:7687
2023-07-03 08:07:19.316  DEBUG  [pool 1] Trying to borrow connection from [localhost:7687]
2023-07-03 08:07:19.316   INFO  [pool 1] Connecting to localhost:7687
2023-07-03 08:07:19.320   INFO  [bolt5 bolt-58@localhost:7687] Connected
2023-07-03 08:07:19.320   INFO  [bolt5 bolt-58@localhost:7687] Retrieving routing table
2023-07-03 08:07:19.320  DEBUG  [pool 1] Returning connection to localhost:7687 {alive:true}
2023-07-03 08:07:19.320  DEBUG  [bolt5 bolt-58@localhost:7687] Resetting connection internal state
2023-07-03 08:07:19.320  DEBUG  [router 1] New routing table for 'neo4j', TTL 300
2023-07-03 08:07:19.320  DEBUG  [session 2] Resolved home database, uses db 'neo4j'
2023-07-03 08:07:19.320  DEBUG  [pool 1] Trying to borrow connection from [localhost:7687]
2023-07-03 08:07:19.321  DEBUG  [pool 1] Returning connection to localhost:7687 {alive:true}
2023-07-03 08:07:19.321  DEBUG  [bolt5 bolt-58@localhost:7687] Resetting connection internal state
2023-07-03 08:07:19.321  DEBUG  [router 1] Cleaning up
2023-07-03 08:07:19.321  DEBUG  [session 2] Closed

Bolt 日志记录可以通过以下方式启用

  • 每个查询,使用配置回调 neo4j.ExecuteQueryBoltLogger()。这适用于使用ExecuteQuery()运行的单个查询。

  • 每个会话,使用配置选项 BoltLogger。这适用于会话中的所有查询。

为使用 ExecuteQuery 运行的查询启用日志记录
result, err := neo4j.ExecuteQuery(ctx, driver,
    "RETURN 42 AS n", nil, neo4j.EagerResultTransformer,
    neo4j.ExecuteQueryWithDatabase("neo4j"),
    neo4j.ExecuteQueryWithBoltLogger(neo4j.ConsoleBoltLogger()))
为会话启用日志记录
session := driver.NewSession(ctx, neo4j.SessionConfig{
    DatabaseName: "neo4j",
    BoltLogger: neo4j.ConsoleBoltLogger(),
})
defer session.Close(ctx)
session.Run(ctx, "RETURN 42 AS n", nil)
Bolt 日志记录输出示例
2023-07-03 07:57:09.929   BOLT  [bolt-53@localhost:7687] C: BEGIN {"db":"neo4j"}
2023-07-03 07:57:09.930   BOLT  [bolt-53@localhost:7687] S: SUCCESS {}
2023-07-03 07:57:09.930   BOLT  [bolt-53@localhost:7687] C: RUN "RETURN 42 AS n" null null
2023-07-03 07:57:09.930   BOLT  [bolt-53@localhost:7687] C: PULL {"n":1000}
2023-07-03 07:57:09.936   BOLT  [bolt-53@localhost:7687] S: SUCCESS {"fields":["n"],"t_first":5}
2023-07-03 07:57:09.937   BOLT  [bolt-53@localhost:7687] S: RECORD [42]
2023-07-03 07:57:09.937   BOLT  [bolt-53@localhost:7687] S: SUCCESS {"t_first":1,"db":"neo4j"}
2023-07-03 07:57:09.937   BOLT  [bolt-53@localhost:7687] C: COMMIT
2023-07-03 07:57:09.938   BOLT  [bolt-53@localhost:7687] S: SUCCESS {"bookmark":"FB:kcwQhRyDJPONRxudy+QyzPSuSAaQ"}

词汇表

LTS

长期支持版本是保证支持一定年限的版本。Neo4j 4.4 是 LTS 版本,Neo4j 5 也将有一个 LTS 版本。

Aura

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

Cypher

Cypher 是 Neo4j 的图查询语言,可让您从数据库中检索数据。它类似于 SQL,但用于图。

APOC

Cypher 上的优秀过程 (APOC) 是一个(许多)函数库,这些函数本身无法用 Cypher 轻松表达。

Bolt

Bolt 是 Neo4j 实例和驱动程序之间交互使用的协议。默认情况下,它侦听端口 7687。

ACID

原子性、一致性、隔离性、持久性 (ACID) 是保证数据库事务可靠处理的属性。符合 ACID 的 DBMS 确保数据库中的数据即使在出现故障时也能保持准确和一致。

最终一致性

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

因果一致性

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

NULL

空标记不是一种类型,而是表示值不存在的占位符。有关更多信息,请参阅Cypher → 使用null

事务

事务是工作单元,要么完全提交,要么在失败时回滚。例如银行转账:它涉及多个步骤,但它们必须全部成功或被撤销,以避免从一个账户中扣除资金但未添加到另一个账户中。

背压

背压是阻碍数据流动的力量。它确保客户端不会被超出其处理能力的数据速度所淹没。

事务函数

事务函数是由ExecuteReadExecuteWrite调用执行的回调函数。驱动程序会在服务器发生故障时自动重新执行回调函数。

DriverWithContext

一个DriverWithContext对象包含建立与Neo4j数据库连接所需的详细信息。