索引对查询性能的影响
搜索性能索引通过解决节点标签/关系类型和属性谓词的特定组合,可以实现更快、更高效的模式匹配。它们由 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
属性上创建一个相关的索引,然后再次运行上述查询,计算具有 baseball
type
值的 PointOfInterest
节点的数量
CREATE INDEX range_index_type FOR (n:PointOfInterest) ON (n.type)
如果在创建索引时没有指定索引类型,Neo4j 将默认创建范围索引。有关创建索引的更多信息,请参阅 创建、显示和删除索引 → 创建索引。 |
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 行(代表数据库中具有设置为
baseball
的type
值的 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
值按数字顺序排列)。这可能对查询性能有重大影响,因为计划程序可能能够利用预先存在的索引顺序,因此不必在查询的后期执行昂贵的排序
操作。
为了演示这种行为,以下查询将过滤掉任何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
运算符访问,该运算符从关系类型索引中获取所有关系及其起点和终点节点)。 -
因此,计划程序必须执行
排序
操作以按距离属性对结果进行排序(在本例中,它需要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
上创建的范围索引。 -
因此,计划不再需要执行
排序
操作以对结果进行排序(因为distance
属性已按索引排序),这大大降低了查询的成本(查询的总内存成本现在为312字节)。
使用多个索引
索引主要用于查找模式的起点。如果查询包含一个MATCH
子句,那么,作为一般规则,计划程序将仅选择最适合该子句中谓词的索引。但是,如果查询包含两个或多个MATCH
子句,则可以使用多个索引。
为了显示在一个查询中使用的多个索引,以下示例将首先在PointOfInterest
节点的lon
(经度)属性上创建一个新索引。然后,它使用一个查询,该查询查找中央公园中威廉·莎士比亚雕像以北的所有PointOfInterest
节点。
CREATE INDEX range_index_lon FOR (n:PointOfInterest) ON (n.lon)
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
属性上的索引查找威廉·莎士比亚
节点,然后使用lon
属性上的索引查找所有具有更大经度值的节点)。
索引和null
值
Neo4j 索引不存储null
值。这意味着计划程序必须能够排除null
值的可能性,以便查询使用索引。
以下查询通过计算所有name
属性未设置的PointOfInterest
节点来演示null
值和索引之间的不相容性
null
name
值的节点PROFILE
MATCH (n:PointOfInterest)
WHERE n.name IS NULL
RETURN count(n) AS 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
值的查询谓词,则可以使用索引。以下查询通过向上面的查询添加子字符串谓词来显示这一点
null
name
值的节点或name
属性包含'William'
的节点PROFILE
MATCH (n:PointOfInterest)
WHERE n.name IS NULL OR n.name CONTAINS 'William'
RETURN count(n) AS 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
PointOfInterest
节点,这些节点没有null
name
值PROFILE
MATCH (n:PointOfInterest)
WHERE n.name IS NOT NULL
RETURN count(n) AS 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
PointOfInterest
节点,这些节点没有null
name
值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 *
。有关更多信息,请参见 创建、显示和删除索引→列出索引的结果列。