索引对查询性能的影响
搜索性能索引通过解决节点标签/关系类型和属性谓词的特定组合,实现更快、更高效的模式匹配。它们通常在查询开始时由 Cypher® 规划器在 MATCH
子句中自动使用,以扫描图以找到最适合开始模式匹配过程的位置。
通过检查查询执行计划,本页将解释在何种场景下使用各种搜索性能索引来提高 Cypher 查询性能。它还将提供何时使用索引的一些通用启发式方法,以及如何避免过度索引的建议。
示例图
本页示例围绕根据 OpenStreetMap 提供的数据在纽约中央公园查找路线和兴趣点展开。数据模型包含两个节点标签
-
OSMNode
(Open Street Map 节点) — 一个具有地理空间属性的连接节点,连接来自特定点的路线。 -
PointOfInterest
—OSMNode
的一个子类别。除了地理空间属性外,这些节点还包含有关中央公园中特定兴趣点的信息,例如雕像、棒球场等。
数据模型还包含一种关系类型:ROUTE
,它指定图中节点之间的距离(以米为单位)。
总计,该图包含 69165 个节点(其中 188 个具有 PointOfInterest
标签)和 152077 个 ROUTE
关系。
令牌查找索引
创建 Neo4j 数据库时,默认存在两个令牌查找索引。它们存储数据库中所有节点标签和关系类型的副本,并且只解决节点标签和关系类型谓词。
以下查询 [1],它统计 type
属性值为 baseball
的 PointOfInterest
节点的数量,将访问节点标签查找索引
PROFILE
MATCH (n:PointOfInterest)
WHERE n.type = 'baseball'
RETURN count(n)
count(n) |
---|
|
行数: 1 |
+-------------------+----+------------------------+----------------+------+---------+----------------+------------------------+-----------+---------------------+ | Operator | Id | Details | Estimated Rows | Rows | DB Hits | Memory (Bytes) | Page Cache Hits/Misses | Time (ms) | Pipeline | +-------------------+----+------------------------+----------------+------+---------+----------------+------------------------+-----------+---------------------+ | +ProduceResults | 0 | `count(n)` | 1 | 1 | 0 | 0 | 0/0 | 0.075 | In Pipeline 1 | | | +----+------------------------+----------------+------+---------+----------------+------------------------+-----------+---------------------+ | +EagerAggregation | 1 | count(n) AS `count(n)` | 1 | 1 | 0 | 32 | | | | | | +----+------------------------+----------------+------+---------+----------------+ | | | | +Filter | 2 | n.type = $autostring_0 | 9 | 26 | 376 | | | | | | | +----+------------------------+----------------+------+---------+----------------+ | | | | +NodeByLabelScan | 3 | n:PointOfInterest | 188 | 188 | 189 | 376 | 116/0 | 8.228 | Fused in Pipeline 0 | +-------------------+----+------------------------+----------------+------+---------+----------------+------------------------+-----------+---------------------+ Total database accesses: 565, total allocated memory: 472
执行计划中值得强调的细节如下
-
NodeByLabelScan
操作符访问节点标签查找索引,生成 188 行,表示数据库中带有PointOfInterest
标签的 188 个节点。 -
该查询需要 565 次数据库命中(每次数据库命中表示查询需要访问数据库的一次实例)。
-
查询在 8 毫秒多一点的时间内完成。
令牌查找索引非常重要,因为它们能提高 Cypher 查询的性能以及其他索引的填充速度,并且删除它们将导致严重的性能下降。在上述示例中,如果不存在节点标签查找索引,NodeByLabelScan
操作符将被 AllNodesScan
取代,后者必须从数据库中读取所有 69165 个节点才能返回结果。
尽管有用,但令牌查找索引很少能满足查询非平凡大小数据库的应用程序的需求,因为它们无法解决任何与属性相关的谓词。
有关令牌查找索引支持的谓词的更多信息,请参阅创建、显示和删除索引 → 令牌查找索引:支持的谓词。
范围索引
范围索引解决大多数类型的谓词,并用于根据值的范围高效检索数据。它们对于处理具有有序、可比较值的属性特别有用。
以下示例首先为 PointOfInterest
节点的 type 属性创建一个相关索引,然后再次运行上述查询,统计 type
值为 baseball
的 PointOfInterest
节点的数量
CREATE INDEX range_index_type FOR (n:PointOfInterest) ON (n.type)
如果在创建索引时未指定索引类型,Neo4j 将默认创建范围索引。有关创建索引的更多信息,请参阅创建、显示和删除索引 → CREATE INDEX。 |
PROFILE
MATCH (n:PointOfInterest)
WHERE n.type = 'baseball'
RETURN count(n)
+-------------------+----+----------------------------------------------------------------+----------------+------+---------+----------------+------------------------+-----------+---------------------+ | Operator | Id | Details | Estimated Rows | Rows | DB Hits | Memory (Bytes) | Page Cache Hits/Misses | Time (ms) | Pipeline | +-------------------+----+----------------------------------------------------------------+----------------+------+---------+----------------+------------------------+-----------+---------------------+ | +ProduceResults | 0 | `count(n)` | 1 | 1 | 0 | 0 | 0/0 | 0.057 | In Pipeline 1 | | | +----+----------------------------------------------------------------+----------------+------+---------+----------------+------------------------+-----------+---------------------+ | +EagerAggregation | 1 | count(n) AS `count(n)` | 1 | 1 | 0 | 32 | | | | | | +----+----------------------------------------------------------------+----------------+------+---------+----------------+ | | | | +NodeIndexSeek | 2 | RANGE INDEX n:PointOfInterest(type) WHERE type = $autostring_0 | 5 | 26 | 27 | 376 | 0/1 | 0.945 | Fused in Pipeline 0 | +-------------------+----+----------------------------------------------------------------+----------------+------+---------+----------------+------------------------+-----------+---------------------+ Total database accesses: 27, total allocated memory: 472
将此查询计划与创建相关范围索引之前生成的计划进行比较,以下内容已更改:
-
NodeByLabelScan 已被 NodeIndexSeek 取代。这仅产生 26 行(表示数据库中
type
值设置为baseball
的 26 个PointOfInterest
节点)。 -
现在查询只需要 27 次数据库命中。
-
查询在不到 1 毫秒内完成——比没有范围索引时快了近 8 倍。
所有这些点都说明了搜索性能索引可以显著提高 Cypher 查询性能这一基本要点。
有关范围索引支持的谓词的更多信息,请参阅创建、显示和删除索引 → 范围索引:支持的谓词。
文本索引
文本索引用于过滤 STRING
属性的查询。
如果给定 STRING
属性上同时存在范围索引和文本索引,则 Cypher 规划器仅在查询使用 CONTAINS
或 ENDS WITH
操作符进行过滤时使用文本索引。在所有其他情况下,将使用范围索引。
为了演示这种行为,需要在同一属性上创建一个文本索引和一个范围索引
CREATE TEXT INDEX text_index_name FOR (n:PointOfInterest) ON (n.name)
CREATE INDEX range_index_name FOR (n:PointOfInterest) ON (n.name)
以下查询过滤所有 name
属性 CONTAINS
'William'
的 PointOfInterest
节点
STRING
属性 CONTAINS
的查询PROFILE
MATCH (n:PointOfInterest)
WHERE n.name CONTAINS 'William'
RETURN n.name AS name, n.type AS type
name | type |
---|---|
|
|
|
|
行数: 2 |
+------------------------+----+----------------------------------------------------------------------+----------------+------+---------+----------------+------------------------+-----------+---------------------+ | Operator | Id | Details | Estimated Rows | Rows | DB Hits | Memory (Bytes) | Page Cache Hits/Misses | Time (ms) | Pipeline | +------------------------+----+----------------------------------------------------------------------+----------------+------+---------+----------------+------------------------+-----------+---------------------+ | +ProduceResults | 0 | name, type | 1 | 2 | 0 | 0 | | | | | | +----+----------------------------------------------------------------------+----------------+------+---------+----------------+ | | | | +Projection | 1 | cache[n.name] AS name, cache[n.type] AS type | 1 | 2 | 0 | | | | | | | +----+----------------------------------------------------------------------+----------------+------+---------+----------------+ | | | | +CacheProperties | 2 | cache[n.type], cache[n.name] | 1 | 2 | 6 | | | | | | | +----+----------------------------------------------------------------------+----------------+------+---------+----------------+ | | | | +NodeIndexContainsScan | 3 | TEXT INDEX n:PointOfInterest(name) WHERE name CONTAINS $autostring_0 | 1 | 2 | 3 | 248 | 4/0 | 53.297 | Fused in Pipeline 0 | +------------------------+----+----------------------------------------------------------------------+----------------+------+---------+----------------+------------------------+-----------+---------------------+ Total database accesses: 9, total allocated memory: 312
计划显示查询使用文本索引来查找所有相关节点。但是,如果查询改为使用 STARTS WITH
操作符而不是 CONTAINS
,查询将改用范围索引
STRING
属性 STARTS WITH
的查询PROFILE
MATCH (n:PointOfInterest)
WHERE n.name STARTS WITH 'William'
RETURN n.name, n.type
+-----------------------+----+-----------------------------------------------------------------------------------------+----------------+------+---------+----------------+------------------------+-----------+---------------------+ | Operator | Id | Details | Estimated Rows | Rows | DB Hits | Memory (Bytes) | Page Cache Hits/Misses | Time (ms) | Pipeline | +-----------------------+----+-----------------------------------------------------------------------------------------+----------------+------+---------+----------------+------------------------+-----------+---------------------+ | +ProduceResults | 0 | `n.name`, `n.type` | 1 | 2 | 0 | 0 | | | | | | +----+-----------------------------------------------------------------------------------------+----------------+------+---------+----------------+ | | | | +Projection | 1 | cache[n.name] AS `n.name`, n.type AS `n.type` | 1 | 2 | 4 | | | | | | | +----+-----------------------------------------------------------------------------------------+----------------+------+---------+----------------+ | | | | +NodeIndexSeekByRange | 2 | RANGE INDEX n:PointOfInterest(name) WHERE name STARTS WITH $autostring_0, cache[n.name] | 1 | 2 | 3 | 248 | 4/1 | 1.276 | Fused in Pipeline 0 | +-----------------------+----+-----------------------------------------------------------------------------------------+----------------+------+---------+----------------+------------------------+-----------+---------------------+ Total database accesses: 7, total allocated memory: 312
这是因为范围索引按字母顺序存储 STRING
值。这意味着,虽然它们对于检索 STRING
的精确匹配或前缀匹配非常高效,但对于后缀和包含搜索则效率较低,因为它们必须扫描所有相关属性以过滤任何匹配项。文本索引不按字母顺序存储 STRING
属性,而是针对后缀和包含搜索进行了优化(更多信息请参见创建文本索引 → 三元索引)。尽管如此,如果在 name 属性上没有范围索引,先前的查询仍然能够利用文本索引。它的效率会低于范围索引,但仍然有用。
有关范围索引排序的更多信息,请参阅范围索引支持的 ORDER BY 部分。
文本索引仅用于精确查询匹配。要执行近似匹配(例如,包括变体和拼写错误),并计算 STRING 值之间的相似度得分,请改用语义 全文索引。 |
有关文本索引支持的谓词的更多信息,请参阅创建、显示和删除索引 → 文本索引:支持的谓词。
确保文本索引使用
为了让规划器使用文本索引,它必须能够确认谓词中包含的属性是 STRING
值。当访问节点或关系中的属性值,或者 MAP
中的值时,这是不可能的,因为 Cypher 不存储这些值的类型信息。为确保在这些情况下使用文本索引,应使用 toString
函数。
WITH {name: 'William Shakespeare'} AS varName
MERGE (:PointOfInterest {name:varName.name})
WITH {name: 'William Shakespeare'} AS varName
MERGE (:PointOfInterest {name: toString(varName.name)})
有关谓词可能包含 null
值时如何确保使用文本索引的信息,请参阅索引和 null
值。
文本索引和 STRING
大小
索引 STRING
属性的大小也与规划器在范围索引和文本索引之间的选择有关。
范围索引的最大键大小限制约为 8 KB。这意味着范围索引不能用于索引大于 8 KB 的 STRING
值。另一方面,文本索引的最大键大小限制约为 32 KB。因此,它们可以用于索引达到该大小的 STRING
值。
有关计算索引大小的信息,请参阅Neo4j 知识库 → 计算 Neo4j 中索引大小的方法。
点索引
以下示例创建一个点索引,该索引随后用于一个查询,返回给定边界框内所有 PointOfInterest
节点的 name
和 type
。
CREATE POINT INDEX point_index_location FOR (n:PointOfInterest) ON (n.location)
point.withinBBox()
函数的查询PROFILE
MATCH (n:PointOfInterest)
WHERE point.withinBBox(
n.location,
point({srid: 4326, x: -73.9723702, y: 40.7697989}),
point({srid: 4326, x: -73.9725659, y: 40.770193}))
RETURN n.name AS name, n.type AS type
name | type |
---|---|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
行数: 8 |
+-----------------------+----+------------------------------------------------------------------------------------------------------+----------------+------+---------+----------------+------------------------+-----------+---------------------+ | Operator | Id | Details | Estimated Rows | Rows | DB Hits | Memory (Bytes) | Page Cache Hits/Misses | Time (ms) | Pipeline | +-----------------------+----+------------------------------------------------------------------------------------------------------+----------------+------+---------+----------------+------------------------+-----------+---------------------+ | +ProduceResults | 0 | `n.name`, `n.type` | 4 | 8 | 0 | 0 | | | | | | +----+------------------------------------------------------------------------------------------------------+----------------+------+---------+----------------+ | | | | +Projection | 1 | cache[n.name] AS `n.name`, cache[n.type] AS `n.type` | 4 | 8 | 0 | | | | | | | +----+------------------------------------------------------------------------------------------------------+----------------+------+---------+----------------+ | | | | +CacheProperties | 2 | cache[n.type], cache[n.name] | 4 | 8 | 24 | | | | | | | +----+------------------------------------------------------------------------------------------------------+----------------+------+---------+----------------+ | | | | +NodeIndexSeekByRange | 3 | POINT INDEX n:PointOfInterest(location) WHERE point.withinBBox(location, point($autoint_0, $autodoub | 4 | 8 | 10 | 248 | 302/0 | 2.619 | Fused in Pipeline 0 | | | | le_1, $autodouble_2), point($autoint_3, $autodouble_4, $autodouble_5)) | | | | | | | | +-----------------------+----+------------------------------------------------------------------------------------------------------+----------------+------+---------+----------------+------------------------+-----------+---------------------+ Total database accesses: 34, total allocated memory: 312
有关点索引支持的谓词的更多信息,请参阅创建、显示和删除索引 → 点索引:支持的谓词。
点索引配置设置
可以配置点索引以仅索引特定地理区域内的属性。这通过在创建点索引时在 OPTIONS
子句的 indexConfig
部分指定以下任一设置来完成
每个设置的 min
和 max
定义了每个坐标系中空间数据的最小和最大边界。
例如,以下索引将仅存储中央公园北部区域的 OSMNodes
。
CREATE POINT INDEX central_park_north
FOR (o:OSMNode) ON (o.location)
OPTIONS {
indexConfig: {
`spatial.wgs-84.min`:[40.7714, -73.9743],
`spatial.wgs-84.max`:[40.7855, -73.9583]
}
}
限制点索引的地理区域可以提高空间查询的性能。这在处理复杂、大型地理空间数据以及空间查询是应用程序功能的重要组成部分时特别有益。
复合索引
可以在单个属性或多个属性上创建范围索引(文本和点索引仅支持单个属性)。后者称为复合索引,如果针对数据库的查询频繁地过滤复合索引所索引的所有属性,则复合索引可能很有用。
以下示例首先为 PointOfInterest
节点的 name
和 type
属性创建复合索引,然后使用 shortestPath 函数查询图,以确定 Zoo School
及其最近的 tennis pitch
之间的路径长度(就图中遍历的关系而言)和地理距离(请注意,图中存在 32 个唯一的 PointOfInterest
tennis pitch
节点)
CREATE INDEX composite_index FOR (n:PointOfInterest) ON (n.name, n.type)
PROFILE
MATCH (tennisPitch: PointOfInterest {name: 'pitch', type: 'tennis'})
WITH tennisPitch
MATCH path = shortestPath((tennisPitch)-[:ROUTE*]-(:PointOfInterest {name: 'Zoo School'}))
WITH path, relationships(path) AS relationships
ORDER BY length(path) ASC
LIMIT 1
UNWIND relationships AS rel
RETURN length(path) AS pathLength, sum(rel.distance) AS geographicalDistance
pathLength | geographicalDistance |
---|---|
|
|
行数: 1 |
+---------------------+----+------------------------------------------------------------------------------------------------------+----------------+------+---------+----------------+------------------------+-----------+------------------+---------------------+ | Operator | Id | Details | Estimated Rows | Rows | DB Hits | Memory (Bytes) | Page Cache Hits/Misses | Time (ms) | Ordered by | Pipeline | +---------------------+----+------------------------------------------------------------------------------------------------------+----------------+------+---------+----------------+------------------------+-----------+------------------+---------------------+ | +ProduceResults | 0 | pathLength, geographicalDistance | 1 | 1 | 0 | 0 | 0/0 | 0.065 | | | | | +----+------------------------------------------------------------------------------------------------------+----------------+------+---------+----------------+------------------------+-----------+ | | | +OrderedAggregation | 1 | length(path) AS pathLength, sum(rel.distance) AS geographicalDistance | 1 | 1 | 50 | 5140 | 31/0 | 4.097 | pathLength ASC | In Pipeline 3 | | | +----+------------------------------------------------------------------------------------------------------+----------------+------+---------+----------------+------------------------+-----------+------------------+---------------------+ | +Unwind | 2 | relationships AS rel | 1 | 25 | 0 | 3112 | 0/0 | 0.180 | | In Pipeline 2 | | | +----+------------------------------------------------------------------------------------------------------+----------------+------+---------+----------------+------------------------+-----------+ +---------------------+ | +Projection | 3 | relationships(path) AS relationships | 0 | 1 | 0 | | 0/0 | 0.050 | | | | | +----+------------------------------------------------------------------------------------------------------+----------------+------+---------+----------------+------------------------+-----------+ | | | +Top | 4 | `length(path)` ASC LIMIT 1 | 0 | 1 | 0 | 57472 | 0/0 | 1.763 | length(path) ASC | In Pipeline 1 | | | +----+------------------------------------------------------------------------------------------------------+----------------+------+---------+----------------+------------------------+-----------+------------------+---------------------+ | +Projection | 5 | length(path) AS `length(path)` | 0 | 32 | 0 | | | | | | | | +----+------------------------------------------------------------------------------------------------------+----------------+------+---------+----------------+ | +------------------+ | | +ShortestPath | 6 | path = (tennisPitch)-[anon_0:ROUTE*]-(anon_1) | 0 | 32 | 181451 | 70080 | | | | | | | +----+------------------------------------------------------------------------------------------------------+----------------+------+---------+----------------+ | +------------------+ | | +MultiNodeIndexSeek | 7 | RANGE INDEX tennisPitch:PointOfInterest(name, type) WHERE name = $autostring_0 AND type = $autostrin | 0 | 31 | 0 | 376 | 131215/1 | 188.723 | | Fused in Pipeline 0 | | | | g_1, RANGE INDEX anon_1:PointOfInterest(name) WHERE name = $autostring_2 | | | | | | | | | +---------------------+----+------------------------------------------------------------------------------------------------------+----------------+------+---------+----------------+------------------------+-----------+------------------+---------------------+ Total database accesses: 181501, total allocated memory: 116040
查询计划显示使用了复合索引,而不是先前在 type
属性上创建的范围索引。这是因为复合索引同时解决了查询的谓词,而单个属性索引只能解决谓词的一部分。
属性顺序和查询规划
与单属性范围索引一样,复合索引支持所有谓词:
-
相等性检查:
n.prop = value
-
列表成员检查:
n.prop IN [value, …]
-
存在性检查:
n.prop IS NOT NULL
-
范围搜索:
n.prop > value
-
前缀搜索:
n.prop STARTS WITH value
然而,在创建复合索引时定义属性的顺序会影响规划器如何使用索引来解决谓词。例如,在 (n.prop1, n.prop2, n.prop3)
上创建的复合索引将生成与在 (n.prop3, n.prop2, n.prop1)
上创建的复合索引不同的查询计划。
以下示例展示了在不同顺序下定义的相同属性上的复合索引将如何生成不同的执行计划
CREATE INDEX composite_2 FOR (n:PointOfInterest) ON (n.lat, n.name, n.type)
请注意创建索引时属性的定义顺序:lat
在前,name
居中,type
在后。
PROFILE
MATCH (n:PointOfInterest)
WHERE n.lat = 40.7697989 AND n.name STARTS WITH 'William' AND n.type IS NOT NULL
RETURN n.name AS name
name |
---|
|
行数: 1 |
+-----------------+----+------------------------------------------------------------------------------------------------------+----------------+------+---------+----------------+------------------------+-----------+---------------------+ | Operator | Id | Details | Estimated Rows | Rows | DB Hits | Memory (Bytes) | Page Cache Hits/Misses | Time (ms) | Pipeline | +-----------------+----+------------------------------------------------------------------------------------------------------+----------------+------+---------+----------------+------------------------+-----------+---------------------+ | +ProduceResults | 0 | name | 0 | 0 | 0 | 0 | | | | | | +----+------------------------------------------------------------------------------------------------------+----------------+------+---------+----------------+ | | | | +Projection | 1 | cache[n.name] AS name | 0 | 0 | 0 | | | | | | | +----+------------------------------------------------------------------------------------------------------+----------------+------+---------+----------------+ | | | | +NodeIndexSeek | 2 | RANGE INDEX n:PointOfInterest(lat, name, type) WHERE lat = $autodouble_0 AND name STARTS WITH $autos | 0 | 0 | 1 | 248 | 0/2 | 1.276 | Fused in Pipeline 0 | | | | tring_1 AND type IS NOT NULL, cache[n.name] | | | | | | | | +-----------------+----+------------------------------------------------------------------------------------------------------+----------------+------+---------+----------------+------------------------+-----------+---------------------+ Total database accesses: 1, total allocated memory: 312
计划显示使用了最近创建的复合索引。它还显示谓词按照查询中指定的方式进行过滤(即对 lat
属性进行相等性检查,对 name
属性进行前缀搜索,以及对 type
属性进行存在性检查)。
然而,如果在创建索引时更改属性的顺序,将生成不同的查询计划。为了演示这种行为,首先需要删除最近创建的 composite_2
索引,然后以不同顺序在相同属性上创建一个新的复合索引。
DROP INDEX composite_2
CREATE INDEX composite_3 FOR (n:PointOfInterest) ON (n.name, n.type, n.lat)
请注意属性的顺序已更改:name
属性现在是复合索引中定义的第一个属性,而 lat
属性则最后被索引。
PROFILE
MATCH (n:PointOfInterest)
WHERE n.lat = 40.769798 AND n.name STARTS WITH 'William' AND n.type IS NOT NULL
RETURN n.name AS name
+-----------------+----+-----------------------------------------------------------------------------------------------------+----------------+------+---------+----------------+------------------------+-----------+---------------------+ | Operator | Id | Details | Estimated Rows | Rows | DB Hits | Memory (Bytes) | Page Cache Hits/Misses | Time (ms) | Pipeline | +-----------------+----+-----------------------------------------------------------------------------------------------------+----------------+------+---------+----------------+------------------------+-----------+---------------------+ | +ProduceResults | 0 | name | 0 | 0 | 0 | 0 | | | | | | +----+-----------------------------------------------------------------------------------------------------+----------------+------+---------+----------------+ | | | | +Projection | 1 | cache[n.name] AS name | 0 | 0 | 0 | | | | | | | +----+-----------------------------------------------------------------------------------------------------+----------------+------+---------+----------------+ | | | | +Filter | 2 | cache[n.lat] = $autodouble_0 | 0 | 0 | 0 | | | | | | | +----+-----------------------------------------------------------------------------------------------------+----------------+------+---------+----------------+ | | | | +NodeIndexSeek | 3 | RANGE INDEX n:PointOfInterest(name, type, lat) WHERE name STARTS WITH $autostring_1 AND type IS NOT | 0 | 2 | 3 | 248 | 2/0 | 0.807 | Fused in Pipeline 0 | | | | NULL AND lat IS NOT NULL, cache[n.name], cache[n.lat] | | | | | | | | +-----------------+----+-----------------------------------------------------------------------------------------------------+----------------+------+---------+----------------+------------------------+-----------+---------------------+ Total database accesses: 3, total allocated memory: 312
此计划现在显示,虽然已使用前缀搜索来解决 name
属性谓词,但 lat
属性谓词不再通过相等性检查解决,而是通过存在性检查和随后的显式过滤操作解决。请注意,如果在重新运行查询之前没有删除 composite_2
索引,规划器将使用它而不是 composite_3
索引。
这是因为,在使用复合索引时,前缀搜索之后的任何谓词都将自动规划为存在性检查谓词。
复合索引规则
-
如果查询包含相等性检查或列表成员检查谓词,则它们必须是创建复合索引时定义的第一个属性。
-
利用复合索引的查询最多可以包含一个范围搜索或前缀搜索谓词。
-
可以有任意数量的存在性检查谓词。
-
前缀搜索或存在性检查之后的任何谓词都将规划为存在性检查。
-
后缀和子字符串搜索谓词可以利用复合索引。但是,它们总是被规划为存在性检查,并且任何后续的查询谓词也将相应地被规划为存在性检查。请注意,如果使用这些谓词,并且任何索引的 (
STRING
) 属性上也存在文本索引,规划器将使用文本索引而不是复合索引。
这些规则在创建复合索引时可能很重要,因为某些检查比其他检查更有效率。例如,规划器对属性执行相等性检查通常比执行存在性检查更有效率。因此,根据查询和应用程序,在创建复合索引时考虑属性的定义顺序可能具有成本效益。
此外,需要重申的是,复合索引只有在谓词过滤了复合索引索引的所有属性时才能使用,并且复合索引只能为范围索引创建。
范围索引支持的 ORDER BY
范围索引按升序存储属性(STRING
值按字母顺序,FLOAT
和 INTEGER
值按数字顺序)。这对查询性能可能产生重要影响,因为规划器可以利用预先存在的索引顺序,从而无需在查询后期执行昂贵的 Sort
操作。
为了演示这种行为,以下查询将过滤掉所有 distance
属性小于 30
的 ROUTE
关系,并使用 ORDER BY 子句按升序数字顺序返回匹配关系的 distance
属性。
PROFILE
MATCH ()-[r:ROUTE]-()
WHERE r.distance < 30
RETURN r.distance AS distance
ORDER BY distance
+---------------------------------+----+--------------------------------+----------------+-------+---------+----------------+------------------------+-----------+--------------+---------------------+ | Operator | Id | Details | Estimated Rows | Rows | DB Hits | Memory (Bytes) | Page Cache Hits/Misses | Time (ms) | Ordered by | Pipeline | +---------------------------------+----+--------------------------------+----------------+-------+---------+----------------+------------------------+-----------+--------------+---------------------+ | +ProduceResults | 0 | distance | 3013 | 6744 | 0 | 0 | 0/0 | 14.397 | | | | | +----+--------------------------------+----------------+-------+---------+----------------+------------------------+-----------+ | | | +Sort | 1 | distance ASC | 3013 | 6744 | 0 | 540472 | 0/0 | 16.844 | distance ASC | In Pipeline 1 | | | +----+--------------------------------+----------------+-------+---------+----------------+------------------------+-----------+--------------+---------------------+ | +Projection | 2 | cache[r.distance] AS distance | 3013 | 6744 | 0 | | | | | | | | +----+--------------------------------+----------------+-------+---------+----------------+ | +--------------+ | | +Filter | 3 | cache[r.distance] < $autoint_0 | 3013 | 6744 | 10041 | | | | | | | | +----+--------------------------------+----------------+-------+---------+----------------+ | +--------------+ | | +UndirectedRelationshipTypeScan | 4 | (anon_0)-[r:ROUTE]-(anon_1) | 10044 | 10041 | 5023 | 376 | 84/0 | 22.397 | | Fused in Pipeline 0 | +---------------------------------+----+--------------------------------+----------------+-------+---------+----------------+------------------------+-----------+--------------+---------------------+ Total database accesses: 15064, total allocated memory: 540808
此计划显示了关于索引和结果排序的两个重要点
-
在此查询中,仅使用了关系类型查找索引(通过
UndirectedRelationshipTypeScan
操作符访问,该操作符从关系类型索引中获取所有关系及其起始和结束节点)。 -
因此,规划器必须执行一个
Sort
操作来按 distance 属性对结果进行排序(在这种情况下,它需要 540472 字节的内存)。
要了解索引如何影响查询计划,首先需要为 distance
属性创建范围索引
CREATE INDEX range_index_relationships FOR ()-[r:ROUTE]-() ON (r.distance)
重新运行查询,现在生成了不同的计划
PROFILE
MATCH ()-[r:ROUTE]-()
WHERE r.distance < 30
RETURN r.distance AS distance
ORDER BY distance
+-----------------------------------------+----+--------------------------------------------------------------------------------------------------+----------------+------+---------+----------------+------------------------+-----------+----------------+---------------------+ | Operator | Id | Details | Estimated Rows | Rows | DB Hits | Memory (Bytes) | Page Cache Hits/Misses | Time (ms) | Ordered by | Pipeline | +-----------------------------------------+----+--------------------------------------------------------------------------------------------------+----------------+------+---------+----------------+------------------------+-----------+----------------+---------------------+ | +ProduceResults | 0 | distance | 301 | 6744 | 0 | 0 | | | | | | | +----+--------------------------------------------------------------------------------------------------+----------------+------+---------+----------------+ | | | | | +Projection | 1 | cache[r.distance] AS distance | 301 | 6744 | 0 | | | | distance ASC | | | | +----+--------------------------------------------------------------------------------------------------+----------------+------+---------+----------------+ | +----------------+ | | +UndirectedRelationshipIndexSeekByRange | 2 | RANGE INDEX (anon_0)-[r:ROUTE(distance)]-(anon_1) WHERE distance < $autoint_0, cache[r.distance] | 301 | 6744 | 3373 | 248 | 2361/10 | 76.542 | r.distance ASC | Fused in Pipeline 0 | +-----------------------------------------+----+--------------------------------------------------------------------------------------------------+----------------+------+---------+----------------+------------------------+-----------+----------------+---------------------+ Total database accesses: 3373, total allocated memory: 312
着重于计划中的相同两点,以下内容已更改:
-
现在使用了最近在关系类型属性
distance
上创建的范围索引。 -
因此,计划不再需要执行
Sort
操作来对结果进行排序(因为distance
属性已经按索引排序),这大大降低了查询成本(查询的总内存成本现在为 312 字节)。
多索引使用
索引主要用于查找模式的起始点。如果查询包含一个 MATCH
子句,那么通常情况下,规划器只会选择最适合该子句中谓词的索引。但是,如果查询包含两个或更多 MATCH
子句,则可以使用多个索引。
为了展示在一个查询中使用的多个索引,以下示例将首先为 PointOfInterest
节点的 lon
(经度)属性创建一个新索引。然后,它使用一个查询,查找中央公园中 William Shakespeare
雕像以北的所有 PointOfInterest
节点。
CREATE INDEX range_index_lon FOR (n:PointOfInterest) ON (n.lon)
William Shakespeare
雕像以北所有 PointOfInterest
节点的查询PROFILE
MATCH (ws:PointOfInterest {name:'William Shakespeare'})
WITH ws
MATCH (poi:PointOfInterest)
WHERE poi.lon > ws.lon
RETURN poi.name AS name
+-------------------------+----+-----------------------------------------------------------------+----------------+------+---------+----------------+------------------------+-----------+---------------------+ | Operator | Id | Details | Estimated Rows | Rows | DB Hits | Memory (Bytes) | Page Cache Hits/Misses | Time (ms) | Pipeline | +-------------------------+----+-----------------------------------------------------------------+----------------+------+---------+----------------+------------------------+-----------+---------------------+ | +ProduceResults | 0 | name | 9 | 143 | 0 | 0 | | | | | | +----+-----------------------------------------------------------------+----------------+------+---------+----------------+ | | | | +Projection | 1 | poi.name AS name | 9 | 143 | 283 | | | | | | | +----+-----------------------------------------------------------------+----------------+------+---------+----------------+ | | | | +Apply | 2 | | 9 | 143 | 0 | | | | | | |\ +----+-----------------------------------------------------------------+----------------+------+---------+----------------+ | | | | | +NodeIndexSeekByRange | 3 | RANGE INDEX poi:PointOfInterest(lon) WHERE lon > ws.lon | 9 | 143 | 146 | 2280 | 233/1 | 1.460 | Fused in Pipeline 1 | | | +----+-----------------------------------------------------------------+----------------+------+---------+----------------+------------------------+-----------+---------------------+ | +NodeIndexSeek | 4 | RANGE INDEX ws:PointOfInterest(name) WHERE name = $autostring_0 | 2 | 1 | 2 | 376 | 1/0 | 0.635 | In Pipeline 0 | +-------------------------+----+-----------------------------------------------------------------+----------------+------+---------+----------------+------------------------+-----------+---------------------+ Total database accesses: 431, total allocated memory: 2616
此计划显示,使用了单独的索引来提高每个 MATCH
子句的性能(首先利用 name
属性上的索引查找 William Shakespeare
节点,然后使用 lon
属性上的索引查找所有具有更大经度值的节点)。
索引和 null
值
Neo4j 索引不存储 null
值。这意味着规划器必须能够排除 null
值的可能性,以便查询可以使用索引。
以下查询通过统计所有 name
属性未设置的 PointOfInterest
节点,演示了 null
值与索引之间的不兼容性
name
值为 null
的节点的查询PROFILE
MATCH (n:PointOfInterest)
WHERE n.name IS NULL
RETURN count(n) AS nodes
nodes |
---|
|
行数: 1 |
+-------------------+----+-------------------+----------------+------+---------+----------------+------------------------+-----------+---------------------+ | Operator | Id | Details | Estimated Rows | Rows | DB Hits | Memory (Bytes) | Page Cache Hits/Misses | Time (ms) | Pipeline | +-------------------+----+-------------------+----------------+------+---------+----------------+------------------------+-----------+---------------------+ | +ProduceResults | 0 | nodes | 1 | 1 | 0 | 0 | 0/0 | 0.012 | In Pipeline 1 | | | +----+-------------------+----------------+------+---------+----------------+------------------------+-----------+---------------------+ | +EagerAggregation | 1 | count(n) AS nodes | 1 | 1 | 0 | 32 | | | | | | +----+-------------------+----------------+------+---------+----------------+ | | | | +Filter | 2 | n.name IS NULL | 141 | 3 | 373 | | | | | | | +----+-------------------+----------------+------+---------+----------------+ | | | | +NodeByLabelScan | 3 | n:PointOfInterest | 188 | 188 | 189 | 376 | 115/0 | 0.769 | Fused in Pipeline 0 | +-------------------+----+-------------------+----------------+------+---------+----------------+------------------------+-----------+---------------------+ Total database accesses: 562, total allocated memory: 472
计划显示 name
属性上的两个可用索引(范围索引和文本索引)都没有被用来解决谓词。
然而,如果添加一个能够排除任何 null
值存在的查询谓词,则可以使用索引。以下查询通过在上述查询中添加一个子字符串谓词来展示这一点
name
值为 null
的节点或 name
属性包含 'William'
的节点的查询PROFILE
MATCH (n:PointOfInterest)
WHERE n.name IS NULL OR n.name CONTAINS 'William'
RETURN count(n) AS nodes
nodes |
---|
|
行数: 1 |
查询结果现在既包括前一个查询中找到的三个 name
值未设置的节点,也包括 name
值包含 William
的两个节点(William Shakespeare
和 William Tecumseh Sherman
)。
+--------------------------+----+----------------------------------------------------------------------+----------------+------+---------+----------------+------------------------+-----------+---------------------+ | Operator | Id | Details | Estimated Rows | Rows | DB Hits | Memory (Bytes) | Page Cache Hits/Misses | Time (ms) | Pipeline | +--------------------------+----+----------------------------------------------------------------------+----------------+------+---------+----------------+------------------------+-----------+---------------------+ | +ProduceResults | 0 | nodes | 1 | 1 | 0 | 0 | 0/0 | 0.010 | In Pipeline 3 | | | +----+----------------------------------------------------------------------+----------------+------+---------+----------------+------------------------+-----------+---------------------+ | +EagerAggregation | 1 | count(n) AS nodes | 1 | 1 | 0 | 32 | | | | | | +----+----------------------------------------------------------------------+----------------+------+---------+----------------+ | | | | +Distinct | 2 | n | 141 | 5 | 0 | 352 | | | | | | +----+----------------------------------------------------------------------+----------------+------+---------+----------------+ | | | | +Union | 3 | | 142 | 5 | 0 | 352 | 0/0 | 0.220 | Fused in Pipeline 2 | | |\ +----+----------------------------------------------------------------------+----------------+------+---------+----------------+------------------------+-----------+---------------------+ | | +NodeIndexContainsScan | 4 | TEXT INDEX n:PointOfInterest(name) WHERE name CONTAINS $autostring_0 | 1 | 2 | 3 | 376 | 4/0 | 0.456 | In Pipeline 1 | | | +----+----------------------------------------------------------------------+----------------+------+---------+----------------+------------------------+-----------+---------------------+ | +Filter | 5 | n.name IS NULL | 141 | 3 | 373 | | | | | | | +----+----------------------------------------------------------------------+----------------+------+---------+----------------+ | | | | +NodeByLabelScan | 6 | n:PointOfInterest | 188 | 188 | 189 | 376 | 115/0 | 0.673 | Fused in Pipeline 0 | +--------------------------+----+----------------------------------------------------------------------+----------------+------+---------+----------------+------------------------+-----------+---------------------+ Total database accesses: 565, total allocated memory: 1352
此计划显示索引仅用于解决 WHERE
子句的第二部分,该部分排除了 null
值的存在。
因此,索引属性中存在 null
值并不会取消索引的使用。只有当规划器无法排除匹配过程中包含任何未设置的属性时,索引的使用才会被取消。
null
值的存在可能无法提前得知,这可能导致索引未被使用的意外情况。然而,有一些策略可以确保索引将被使用。
属性存在性检查
确保使用索引的一种方法是,通过在查询属性后附加 IS NOT NULL
来显式过滤掉所有 null
值。以下示例使用与上述相同的查询,但在 WHERE
子句中将 IS NULL
替换为 IS NOT NULL
null
name
值的 PointOfInterest
节点的查询PROFILE
MATCH (n:PointOfInterest)
WHERE n.name IS NOT NULL
RETURN count(n) AS nodes
nodes |
---|
|
行数: 1 |
+-------------------+----+------------------------------------------------------------+----------------+------+---------+----------------+------------------------+-----------+---------------------+ | Operator | Id | Details | Estimated Rows | Rows | DB Hits | Memory (Bytes) | Page Cache Hits/Misses | Time (ms) | Pipeline | +-------------------+----+------------------------------------------------------------+----------------+------+---------+----------------+------------------------+-----------+---------------------+ | +ProduceResults | 0 | nodes | 1 | 1 | 0 | 0 | 0/0 | 0.013 | In Pipeline 1 | | | +----+------------------------------------------------------------+----------------+------+---------+----------------+------------------------+-----------+---------------------+ | +EagerAggregation | 1 | count(n) AS nodes | 1 | 1 | 0 | 32 | | | | | | +----+------------------------------------------------------------+----------------+------+---------+----------------+ | | | | +NodeIndexScan | 2 | RANGE INDEX n:PointOfInterest(name) WHERE name IS NOT NULL | 185 | 185 | 186 | 376 | 0/1 | 0.691 | Fused in Pipeline 0 | +-------------------+----+------------------------------------------------------------+----------------+------+---------+----------------+------------------------+-----------+---------------------+ Total database accesses: 186, total allocated memory: 472
此计划显示,先前在 name
属性上创建的范围索引现在被用于解决谓词。
文本索引和类型谓词表达式
文本索引要求谓词只包含 STRING
属性。
在查询的任何属性可能是不兼容类型或 null
而非 STRING
值的情况下使用文本索引,请在查询中添加类型谓词表达式 IS :: STRING NOT NULL
(或其别名,在 Neo4j 5.14 中引入的 IS :: STRING!
)。这将强制检查属性的存在性及其 STRING
类型,丢弃属性缺失或非 STRING
类型的任何行,从而启用文本索引的使用。
例如,如果将上一个查询中的 WHERE
谓词修改为附加 IS :: STRING NOT NULL
,则会使用文本索引而不是范围索引(范围索引不支持类型谓词表达式)
PROFILE
MATCH (n:PointOfInterest)
WHERE n.name IS :: STRING NOT NULL
RETURN count(n) AS nodes
+-------------------+----+-----------------------------------------------------------+----------------+------+---------+----------------+------------------------+-----------+---------------------+ | Operator | Id | Details | Estimated Rows | Rows | DB Hits | Memory (Bytes) | Page Cache Hits/Misses | Time (ms) | Pipeline | +-------------------+----+-----------------------------------------------------------+----------------+------+---------+----------------+------------------------+-----------+---------------------+ | +ProduceResults | 0 | nodes | 1 | 1 | 0 | 0 | 0/0 | 0.009 | In Pipeline 1 | | | +----+-----------------------------------------------------------+----------------+------+---------+----------------+------------------------+-----------+---------------------+ | +EagerAggregation | 1 | count(n) AS nodes | 1 | 1 | 0 | 32 | | | | | | +----+-----------------------------------------------------------+----------------+------+---------+----------------+ | | | | +NodeIndexScan | 2 | TEXT INDEX n:PointOfInterest(name) WHERE name IS NOT NULL | 185 | 185 | 186 | 376 | 0/0 | 0.343 | Fused in Pipeline 0 | +-------------------+----+-----------------------------------------------------------+----------------+------+---------+----------------+------------------------+-----------+---------------------+ Total database accesses: 186, total allocated memory: 472
虽然类型谓词表达式在 Neo4j 5.9 中引入,但 IS :: STRING NOT NULL 语法仅在 Neo4j 5.15 中成为索引兼容谓词。更多信息,请参阅有关类型谓词表达式的页面。 |
通过 toString
函数也可以将表达式转换为 STRING
值,从而帮助规划器选择文本索引。
属性类型约束
对于仅与特定类型(即文本和点索引)兼容的索引,Cypher 规划器需要推断谓词对于不兼容的值将评估为 null
,以便使用该索引。如果谓词未明确定义为所需类型(STRING
或 POINT
),则可能导致文本或点索引不被使用的情况。
由于属性类型约束保证属性始终是相同类型,因此它们可以用于扩展文本和点索引与谓词兼容的场景。
为了说明这一点,以下示例将首先删除 name
属性上现有的范围索引(这是必需的,因为属性类型约束只扩展了类型特定索引的兼容性——范围索引不受值类型限制)。然后,它将在创建属性类型约束之前和之后,使用带有 name
属性(已存在先前创建的文本索引)的 WHERE
谓词运行相同的查询,并比较生成的执行计划。
DROP INDEX range_index_name
null
name
值的 PointOfInterest
节点的查询PROFILE
MATCH (n:PointOfInterest)
WHERE n.name IS NOT NULL
RETURN count(n) AS nodes
+-------------------+----+--------------------+----------------+------+---------+----------------+------------------------+-----------+---------------------+ | Operator | Id | Details | Estimated Rows | Rows | DB Hits | Memory (Bytes) | Page Cache Hits/Misses | Time (ms) | Pipeline | +-------------------+----+--------------------+----------------+------+---------+----------------+------------------------+-----------+---------------------+ | +ProduceResults | 0 | nodes | 1 | 1 | 0 | 0 | 0/0 | 0.012 | In Pipeline 1 | | | +----+--------------------+----------------+------+---------+----------------+------------------------+-----------+---------------------+ | +EagerAggregation | 1 | count(n) AS nodes | 1 | 1 | 0 | 32 | | | | | | +----+--------------------+----------------+------+---------+----------------+ | | | | +Filter | 2 | n.name IS NOT NULL | 187 | 185 | 373 | | | | | | | +----+--------------------+----------------+------+---------+----------------+ | | | | +NodeByLabelScan | 3 | n:PointOfInterest | 188 | 188 | 189 | 376 | 259/0 | 0.363 | Fused in Pipeline 0 | +-------------------+----+--------------------+----------------+------+---------+----------------+------------------------+-----------+---------------------+ Total database accesses: 562, total allocated memory: 472
此计划显示,name
属性上可用的文本索引未用于解决谓词。这是因为规划器无法推断所有 name
值都是 STRING
类型。
然而,如果创建属性类型约束以确保所有 name
属性都具有 STRING
值,则会生成不同的查询计划。
name
属性上创建 STRING
类型约束CREATE CONSTRAINT type_constraint
FOR (n:PointOfInterest) REQUIRE n.name IS :: STRING
PROFILE
MATCH (n:PointOfInterest)
WHERE n.name IS NOT NULL
RETURN count(n) AS nodes
+-------------------+----+-----------------------------------------------------------+----------------+------+---------+----------------+------------------------+-----------+---------------------+ | Operator | Id | Details | Estimated Rows | Rows | DB Hits | Memory (Bytes) | Page Cache Hits/Misses | Time (ms) | Pipeline | +-------------------+----+-----------------------------------------------------------+----------------+------+---------+----------------+------------------------+-----------+---------------------+ | +ProduceResults | 0 | nodes | 1 | 1 | 0 | 0 | 0/0 | 0.013 | In Pipeline 1 | | | +----+-----------------------------------------------------------+----------------+------+---------+----------------+------------------------+-----------+---------------------+ | +EagerAggregation | 1 | count(n) AS nodes | 1 | 1 | 0 | 32 | | | | | | +----+-----------------------------------------------------------+----------------+------+---------+----------------+ | | | | +NodeIndexScan | 2 | TEXT INDEX n:PointOfInterest(name) WHERE name IS NOT NULL | 187 | 185 | 186 | 376 | 0/0 | 0.328 | Fused in Pipeline 0 | +-------------------+----+-----------------------------------------------------------+----------------+------+---------+----------------+------------------------+-----------+---------------------+ Total database accesses: 186, total allocated memory: 472
由于 name
属性上的属性类型约束,规划器现在能够推断所有 name
属性都是 STRING
类型,因此可以使用可用的文本索引。
如果创建属性类型约束以确保所有属性都是 POINT
值,则点索引也可以以相同的方式进行扩展。
请注意,属性存在性约束目前不支持以同样的方式利用索引。
启发式:决定要索引什么
虽然不可能精确地指出搜索性能索引何时对特定用例有益,但以下几点提供了一些有用的启发式方法,以判断何时创建索引可以提高查询性能
-
频繁的基于属性的查询:如果某些属性经常用于过滤或匹配,请考虑在其上创建索引。
-
性能优化:如果某些查询太慢,请重新检查正在过滤的属性,并考虑为那些可能导致瓶颈的属性创建索引。
-
高基数属性:高基数属性具有许多不同的值(例如,唯一标识符、时间戳或用户名)。旨在检索此类属性的查询可能会从索引中受益。
-
复杂查询:如果查询遍历图中复杂的路径(例如,涉及多个跳跃和多层过滤),则为这些查询中使用的属性添加索引可以提高查询性能。
-
实验和测试:最好尝试不同的索引和查询模式,并测量关键查询在有索引和无索引情况下的性能,以评估其有效性。
过度索引:考虑因素和解决方案
搜索性能索引可以显著提高查询性能。然而,出于以下原因,应谨慎使用它们:
-
存储空间:因为每个索引是主数据库中数据的辅助副本,所以每个索引本质上将索引数据占用的存储空间加倍。
-
写入查询变慢:添加索引会影响写入查询的性能。这是因为索引会在每次写入查询时更新。如果系统需要快速执行大量写入操作,那么在受影响的数据实体上拥有索引可能会适得其反。换句话说,如果写入性能对于特定用例至关重要,那么仅在读取需要时添加索引可能更有益。
由于这两点,决定索引什么(以及不索引什么)是一项重要且不简单的任务。
跟踪索引使用情况:lastRead
、readCount
和 trackedSince
未使用的索引会占用不必要的存储空间,删除它们可能是有益的。然而,了解数据库查询中最常使用哪些索引可能很困难。从 Neo4j 5.8 开始,SHOW INDEX
命令返回的三个相关列可以帮助识别冗余索引
-
lastRead
:返回索引上次用于读取的时间。 -
readCount
:返回向该索引发出的读取查询数量。 -
trackedSince
返回索引使用统计跟踪开始的时间。[2]
要返回数据库中索引的这些值(以及其他相关信息),请运行以下查询
SHOW INDEX YIELD name, type, entityType, labelsOrTypes, properties, lastRead, readCount, trackedSince
如果识别出任何未使用的索引,使用 DROP INDEX
命令删除它们可能会有益。
总结
-
范围索引可用于解决大多数谓词。
-
文本索引在
STRING
属性的CONTAINS
和ENDS WITH
谓词中优于范围索引,如果查询的STRING
属性超过 8 KB 也如此。 -
点索引用于查询根据距离和边界框进行过滤的情况。
-
令牌查找索引只解决节点标签和关系类型谓词。它们不解决任何属性谓词。删除令牌查找索引将对查询性能产生负面影响。
-
复合索引仅在查询过滤复合索引所索引的所有属性时使用。创建复合索引时属性的定义顺序会影响规划器解决查询谓词的方式。
-
使用
ORDER BY
对结果进行排序的查询可以利用范围索引中预先存在的顺序,从而提高查询性能。 -
如果规划器认为对查询性能有益,Cypher 查询可以使用多个索引。
-
Neo4j 索引不存储
null
值,规划器必须能够排除任何包含null
值的实体,以便使用索引。有几种策略可以确保使用索引。 -
SHOW INDEX
命令返回的lastRead
、readCount
和trackedSince
列可用于识别占用不必要空间的冗余索引。
trackedSince
列不是 SHOW INDEXES
命令的默认返回列。要返回此列和所有其他非默认列,请使用 SHOW INDEXES YIELD *
。更多信息,请参阅创建、显示和删除索引 → 列出索引的结果列。