性能建议
始终指定目标数据库
在所有查询中指定目标数据库,使用database
参数,在Driver.executeQuery()
调用中以及创建新的会话时。如果未提供数据库,驱动程序必须向服务器发送额外的请求以确定默认数据库是什么。对于单个查询,开销很小,但在数百个查询中会变得很大。
良好实践
await driver.executeQuery('<QUERY>', {}, {database: '<DB NAME>'})
driver.session({database: '<DB NAME>'})
不良实践
await driver.executeQuery('<QUERY>')
driver.session()
了解事务的成本
当通过.executeQuery()
或通过.executeRead/Qrite()
提交查询时,服务器会自动将它们包装到一个事务中。此行为确保数据库始终处于一致状态,无论事务执行过程中发生什么情况(断电、软件崩溃等)。
在多个查询周围创建安全执行上下文会产生在驱动程序只是向服务器发送查询并希望它们能够通过时不存在的开销。开销很小,但随着查询数量的增加而累积。因此,如果您的用例更重视吞吐量而不是数据完整性,则可以通过在单个(自动提交)事务中运行所有查询来提取更多性能。您可以通过创建一个会话并使用session.run()
运行任意数量的查询来做到这一点。
let session = driver.session({database: '<DB NAME>'})
for(let i=0; i<1000; i++) {
await session.run("<QUERY>")
}
session.close()
for(let i=0; i<1000; i++) {
await driver.executeQuery("<QUERY>", {}, {database: '<DB NAME>'})
// or session.executeRead/Write() calls
}
将读取查询路由到集群读取器
在集群中,将读取查询路由到辅助节点。您可以通过以下方式执行此操作:
-
在
Driver.executeQuery()
调用中将routing: READ
设置为配置 -
使用
Session.executeRead()
而不是Session.executeWrite()
(对于托管事务) -
在创建新会话时设置
AccessMode: neo4j.AccessModeRead
(对于显式事务)。
良好实践
await driver.executeQuery(
'MATCH (p:Person) RETURN p.name',
{},
{
routing: 'READ', // short for neo4j.routing.READ
database: 'neo4j'
}
)
let session = driver.session({ database: 'neo4j' })
await session.executeRead(async tx => {
return await tx.run('MATCH (p:Person) RETURN p.name', {})
})
不良实践
await driver.executeQuery(
'MATCH (p:Person) RETURN p.name',
{},
{
database: 'neo4j'
}
)
// defaults to routing = writers
let session = driver.session({ database: 'neo4j' })
await session.executeRead(async tx => {
return await tx.run('MATCH (p:Person) RETURN p.name', {})
})
// don't ask to write on a read-only operation
创建索引
为您经常过滤的属性创建索引。例如,如果您经常按name
属性查找Person
节点,则在Person.name
上创建索引将很有帮助。您可以使用CREATE INDEX
Cypher 函数为节点和关系创建索引。
// Create an index on Person.name
await driver.executeQuery('CREATE INDEX personName FOR (n:Person) ON (n.name)')
有关更多信息,请参阅索引以提高搜索性能。
分析查询
分析您的查询以找到可以提高性能的查询。您可以通过在查询前面加上PROFILE
来分析查询。服务器输出在ResultSummary
对象的profile
属性中可用。
const result = await driver.executeQuery('PROFILE MATCH (p {name: $name}) RETURN p', { name: 'Alice' })
console.log(result.summary.profile.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
*/
如果某些查询非常慢,以至于您甚至无法在合理的时间内运行它们,则可以将它们前面加上EXPLAIN
而不是PROFILE
。这将返回服务器将用于运行查询的计划,但不会执行它。服务器输出在ResultSummary
对象的plan
属性中可用。
const result = await driver.executeQuery('EXPLAIN MATCH (p {name: $name}) RETURN p', { name: 'Alice' })
console.log(result.summary.plan.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: ?
*/
指定节点标签
在所有查询中指定节点标签。要了解如何组合标签,请参阅Cypher → 标签表达式。
良好实践
await driver.executeQuery(
'MATCH (p:Person|Animal {name: $name}) RETURN p',
{ name: 'Alice' }
)
let session = driver.session({database: '<DB NAME>'})
await session.run(
'MATCH (p:Person|Animal {name: $name}) RETURN p',
{ name: 'Alice' }
)
不良实践
await driver.executeQuery(
'MATCH (p {name: $name}) RETURN p',
{ name: 'Alice' }
)
let session = driver.session({database: '<DB NAME>'})
await session.run(
'MATCH (p {name: $name}) RETURN p',
{ name: 'Alice' }
)
批量数据创建
良好实践
numbers = []
for(let i=0; i<10000; i++) {
numbers.push({value: Math.random()})
}
await driver.executeQuery(`
WITH $numbers AS batch
UNWIND batch AS node
MERGE (n:Number {value: node.value})
`, { numbers: numbers }
)
不良实践
for(let i=0; i<10000; i++) {
await driver.executeQuery(
'MERGE (:Number {value: $value})',
{ value: Math.random() }
)
}
将大量数据首次导入到新数据库中最有效的方法是neo4j-admin database import 命令。 |
使用查询参数
使用查询参数而不是将值硬编码或连接到查询中。这允许利用数据库的查询缓存。
良好实践
await driver.executeQuery(
'MATCH (p:Person {name: $name}) RETURN p',
{ name: 'Alice' } // query parameters
)
let session = driver.session({database: '<DB NAME>'})
await session.run(
'MATCH (p:Person {name: $name}) RETURN p',
{ name: 'Alice' } // query parameters
)
不良实践
await driver.executeQuery('MATCH (p:Person {name: "Alice"}) RETURN p')
let name = "Alice"
await driver.executeQuery('MATCH (p:Person {name: "' + name + '"}) RETURN p')
let session = driver.session({database: '<DB NAME>'})
await session.run(
'MATCH (p:Person {name: "Alice"}) RETURN p',
// or 'MATCH (p:Person {name: ' + name + '}) RETURN p'
{}
)
并发
使用异步查询。如果您在应用程序中并行化复杂且耗时的查询,这可能会对性能产生更大的影响,但如果您运行许多简单的查询,则影响不大。
过滤通知
词汇表
- 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
。 - 事务
-
事务是工作单元,要么全部提交,要么在失败时回滚。一个例子是银行转账:它涉及多个步骤,但它们必须全部成功或被撤销,以避免从一个账户中扣除资金但未添加到另一个账户中。
- 背压
-
背压是反对数据流动的力。它确保客户端不会被超出其处理能力的数据所淹没。
- 事务函数
-
事务函数是
executeRead
或executeWrite
调用执行的回调。如果服务器发生故障,驱动程序会自动重新执行回调。 - 驱动程序
-
一个
Driver
对象包含建立与 Neo4j 数据库连接所需的详细信息。