The use of indexes
It is important to have a fundamental understanding of how indexes operate before using them to tune your Cypher® queries. This section describes the query plans that result from different index scenarios.
Node indexes and relationship indexes operate in the same way. Therefore, node and relationship indexes are used interchangeably in this section.
For instructions on how to create and maintain indexes, refer to Indexes for search performance.
Index types and predicate compatibility
Generally, an index solves some combination of a label/relationship type predicate and property predicates at the same time. There are different types of indexes available in Neo4j and these are compatible with different property predicates.
Indexes are most often used for MATCH
and OPTIONAL MATCH
clauses that combine a label/relationship type predicate with a property predicate.
Therefore, it is important to know what kind of predicates can be solved by the different indexes.
The different index types used for search performance are:
-
LOOKUP
-
RANGE
-
POINT
-
TEXT
-
BTREE
Removed
The |
LOOKUP indexes
LOOKUP
indexes are present by default and solve only node label and relationship type predicates:
Predicate | Syntax (example) |
---|---|
Node label predicate. |
|
Node label predicate. |
|
Relationship type predicate. |
|
Relationship type predicate. |
|
|
RANGE indexes
In combination with node label and relationship type predicates, RANGE
indexes support most types of predicates:
Predicate | Syntax |
---|---|
Equality check. |
|
List membership check. |
|
Existence check. |
|
Range search. |
|
Prefix search. |
|
POINT indexes
In combination with node label and relationship type predicates, POINT
indexes only solve predicates operating on points.
Therefore, POINT
indexes are only used when it is known that the predicate evaluates to null
for all non-point values.
POINT
indexes only support point type predicates:
Predicate | Syntax |
---|---|
Property point value. |
|
Within bounding box. |
|
Distance. |
|
As of Neo4j 5.11, the above set of predicates can be extended with the use of type constraints. For more information, see Extending index compatibility with type constraints. |
TEXT indexes
In combination with node label and relationship type predicates, TEXT
indexes only solve predicates operating on strings.
That means that TEXT
indexes are only used when it is known that the predicate evaluates to null
for all non-string values.
Predicates that only operate on strings are always solvable by a TEXT
index:
-
STARTS WITH
-
ENDS WITH
-
CONTAINS
However, other predicates are only used when it is known that the property is compared to a string:
-
n.prop = "string"
-
n.prop IN ["a", "b", "c"]
This means that a TEXT
index is not able to solve e.g. a.prop = b.prop
, unless a type constraint also exists on the property.
In summary, TEXT
indexes support the following predicates:
Predicate | Syntax |
---|---|
Equality check. |
|
List membership check. |
|
Prefix search. |
|
Suffix search. |
|
Substring search. |
|
As of Neo4j 5.11, the above set of predicates can be extended with the use of type constraints, see Extending index compatibility with type constraints. |
In some cases, the system cannot determine whether an expression is of type string.
For example when the compared value is a parameter:
MATCH (n:Label) WHERE n.prop = $param
Such queries can be modified to provide this information. Depending on how values that are not of type string should be treated, there are two options:
-
If rows in which the expression is not of type string should be discarded, then adding
WHERE <expression> STARTS WITH ''
is the right option:MATCH (n:Label) WHERE $param STARTS WITH '' AND n.prop = $param
-
If expressions which are not of type string should be converted to string, then wrapping these in
toString(<expression>)
is the right choice:MATCH (n:Label) WHERE n.prop = toString($param)
-
If it is known that the property is always of type
STRING
, then it’s also possible to add a type constraint to help the planner.
Extending index compatibility with type constraints
This feature was introduced in Neo4j 5.11.
For indexes that are compatible only with specific types (i.e. TEXT
and POINT
indexes), the planner needs to be able to deduce that a predicate will evaluate to null
for non-compatible values.
Since type constraints guarantee that a property is always of the same type, they can be used to extend the scenarios for which TEXT
indexes and POINT
indexes are compatible with a predicate.
For example, if the property prop
in the below query has been constrained to have type STRING
, then a TEXT
index can also be planned for the IS NOT NULL
predicate:
MATCH (n: Label) WHERE n.prop IS NOT NULL
Similarly, if the property had been constrained to have the type POINT
, then a POINT
index could have been used.
Index preference
When multiple indexes are available and able to solve a predicate, there is an order defined that decides which index to use.
It is defined as such:
-
TEXT
indexes are used overRANGE
andPOINT
indexes forCONTAINS
andENDS WITH
. -
POINT
indexes are used overRANGE
andTEXT
indexes for distance and within a bounding box. -
RANGE
indexes are preferred overTEXT
andPOINT
indexes in all other cases.
LOOKUP
indexes are not defined in this order since they never solve the same set of predicates as the other indexes.
Examples:
Node label LOOKUP index example
In the example below, a node LOOKUP
index is available.
PROFILE
MATCH (person:Person)
RETURN person
Planner COST Runtime PIPELINED Runtime version 5.14 Batch size 128 +------------------+---------------+----------------+------+---------+----------------+------------------------+-----------+---------------------+ | Operator | Details | Estimated Rows | Rows | DB Hits | Memory (Bytes) | Page Cache Hits/Misses | Time (ms) | Pipeline | +------------------+---------------+----------------+------+---------+----------------+------------------------+-----------+---------------------+ | +ProduceResults | person | 42 | 42 | 0 | | | | | | | +---------------+----------------+------+---------+----------------+ | | | | +NodeByLabelScan | person:Person | 42 | 42 | 43 | 120 | 2/1 | 0.565 | Fused in Pipeline 0 | +------------------+---------------+----------------+------+---------+----------------+------------------------+-----------+---------------------+ Total database accesses: 43, total allocated memory: 184
Relationship type LOOKUP index example
In the example below, a relationship LOOKUP
index is available.
PROFILE
MATCH ()-[r:KNOWS]->()
RETURN r
Planner COST Runtime PIPELINED Runtime version 5.14 Batch size 128 +-------------------------------+------------------------------+----------------+------+---------+----------------+------------------------+-----------+---------------------+ | Operator | Details | Estimated Rows | Rows | DB Hits | Memory (Bytes) | Page Cache Hits/Misses | Time (ms) | Pipeline | +-------------------------------+------------------------------+----------------+------+---------+----------------+------------------------+-----------+---------------------+ | +ProduceResults | r | 22 | 22 | 0 | | | | | | | +------------------------------+----------------+------+---------+----------------+ | | | | +DirectedRelationshipTypeScan | (anon_0)-[r:KNOWS]->(anon_1) | 22 | 22 | 23 | 120 | 3/1 | 0.915 | Fused in Pipeline 0 | +-------------------------------+------------------------------+----------------+------+---------+----------------+------------------------+-----------+---------------------+ Total database accesses: 23, total allocated memory: 184
Node RANGE index example
In the example below, a Person(firstname)
node RANGE
index is available.
PROFILE
MATCH (person:Person {firstname: 'Andy'})
RETURN person
Planner COST Runtime PIPELINED Runtime version 5.14 Batch size 128 +-----------------+----------------------------------------------------------------------+----------------+------+---------+----------------+------------------------+-----------+---------------------+ | Operator | Details | Estimated Rows | Rows | DB Hits | Memory (Bytes) | Page Cache Hits/Misses | Time (ms) | Pipeline | +-----------------+----------------------------------------------------------------------+----------------+------+---------+----------------+------------------------+-----------+---------------------+ | +ProduceResults | person | 1 | 1 | 0 | | | | | | | +----------------------------------------------------------------------+----------------+------+---------+----------------+ | | | | +NodeIndexSeek | RANGE INDEX person:Person(firstname) WHERE firstname = $autostring_0 | 1 | 1 | 2 | 120 | 2/1 | 0.635 | Fused in Pipeline 0 | +-----------------+----------------------------------------------------------------------+----------------+------+---------+----------------+------------------------+-----------+---------------------+ Total database accesses: 2, total allocated memory: 184
Relationship RANGE index example
In this example, a KNOWS(since)
relationship RANGE
index is available.
PROFILE
MATCH (person)-[relationship:KNOWS {since: 1992}]->(friend)
RETURN person, friend
Planner COST Runtime PIPELINED Runtime version 5.14 Batch size 128 +--------------------------------+-------------------------------------------------------------------------------------+----------------+------+---------+----------------+------------------------+-----------+---------------------+ | Operator | Details | Estimated Rows | Rows | DB Hits | Memory (Bytes) | Page Cache Hits/Misses | Time (ms) | Pipeline | +--------------------------------+-------------------------------------------------------------------------------------+----------------+------+---------+----------------+------------------------+-----------+---------------------+ | +ProduceResults | person, friend | 1 | 1 | 0 | | | | | | | +-------------------------------------------------------------------------------------+----------------+------+---------+----------------+ | | | | +DirectedRelationshipIndexSeek | RANGE INDEX (person)-[relationship:KNOWS(since)]->(friend) WHERE since = $autoint_0 | 1 | 1 | 2 | 120 | 2/1 | 0.413 | Fused in Pipeline 0 | +--------------------------------+-------------------------------------------------------------------------------------+----------------+------+---------+----------------+------------------------+-----------+---------------------+ Total database accesses: 2, total allocated memory: 184
Node TEXT index
In the example below, a Person(surname)
node TEXT
index is available.
PROFILE
MATCH (person:Person {surname: 'Smith'})
RETURN person
Planner COST Runtime PIPELINED Runtime version 5.14 Batch size 128 +-----------------+-----------------------------------------------------------------+----------------+------+---------+----------------+------------------------+-----------+---------------------+ | Operator | Details | Estimated Rows | Rows | DB Hits | Memory (Bytes) | Page Cache Hits/Misses | Time (ms) | Pipeline | +-----------------+-----------------------------------------------------------------+----------------+------+---------+----------------+------------------------+-----------+---------------------+ | +ProduceResults | person | 1 | 1 | 0 | | | | | | | +-----------------------------------------------------------------+----------------+------+---------+----------------+ | | | | +NodeIndexSeek | TEXT INDEX person:Person(surname) WHERE surname = $autostring_0 | 1 | 1 | 2 | 120 | 2/0 | 0.446 | Fused in Pipeline 0 | +-----------------+-----------------------------------------------------------------+----------------+------+---------+----------------+------------------------+-----------+---------------------+ Total database accesses: 2, total allocated memory: 184
Relationship TEXT index
In this example, a KNOWS(metIn)
relationship TEXT
index is available.
PROFILE
MATCH (person)-[relationship:KNOWS {metIn: 'Malmo'} ]->(friend)
RETURN person, friend
Planner COST Runtime PIPELINED Runtime version 5.14 Batch size 128 +--------------------------------+---------------------------------------------------------------------------------------+----------------+------+---------+----------------+------------------------+-----------+---------------------+ | Operator | Details | Estimated Rows | Rows | DB Hits | Memory (Bytes) | Page Cache Hits/Misses | Time (ms) | Pipeline | +--------------------------------+---------------------------------------------------------------------------------------+----------------+------+---------+----------------+------------------------+-----------+---------------------+ | +ProduceResults | person, friend | 1 | 1 | 0 | | | | | | | +---------------------------------------------------------------------------------------+----------------+------+---------+----------------+ | | | | +DirectedRelationshipIndexSeek | TEXT INDEX (person)-[relationship:KNOWS(metIn)]->(friend) WHERE metIn = $autostring_0 | 1 | 1 | 2 | 120 | 2/0 | 0.691 | Fused in Pipeline 0 | +--------------------------------+---------------------------------------------------------------------------------------+----------------+------+---------+----------------+------------------------+-----------+---------------------+ Total database accesses: 2, total allocated memory: 184
Multiple available index types
In the example below, both a Person(middlename)
node TEXT
index and a Person(middlename)
node RANGE
index are available.
The RANGE
node index is chosen.
PROFILE
MATCH (person:Person {middlename: 'Ron'})
RETURN person
Planner COST Runtime PIPELINED Runtime version 5.14 Batch size 128 +-----------------+------------------------------------------------------------------------+----------------+------+---------+----------------+------------------------+-----------+---------------------+ | Operator | Details | Estimated Rows | Rows | DB Hits | Memory (Bytes) | Page Cache Hits/Misses | Time (ms) | Pipeline | +-----------------+------------------------------------------------------------------------+----------------+------+---------+----------------+------------------------+-----------+---------------------+ | +ProduceResults | person | 1 | 1 | 0 | | | | | | | +------------------------------------------------------------------------+----------------+------+---------+----------------+ | | | | +NodeIndexSeek | RANGE INDEX person:Person(middlename) WHERE middlename = $autostring_0 | 1 | 1 | 2 | 120 | 2/1 | 0.423 | Fused in Pipeline 0 | +-----------------+------------------------------------------------------------------------+----------------+------+---------+----------------+------------------------+-----------+---------------------+ Total database accesses: 2, total allocated memory: 184
Equality check using WHERE
(single-property index)
A query containing equality comparisons of a single indexed property in the WHERE
clause is backed automatically by the index.
It is also possible for a query with multiple OR
predicates to use multiple indexes, if indexes exist on the properties.
For example, if indexes exist on both :Label(p1)
and :Label(p2)
, MATCH (n:Label) WHERE n.p1 = 1 OR n.p2 = 2 RETURN n
will use both indexes.
MATCH (person:Person)
WHERE person.firstname = 'Andy'
RETURN person
Planner COST Runtime PIPELINED Runtime version 5.14 Batch size 128 +-----------------+----------------------------------------------------------------------+----------------+------+---------+----------------+------------------------+-----------+---------------------+ | Operator | Details | Estimated Rows | Rows | DB Hits | Memory (Bytes) | Page Cache Hits/Misses | Time (ms) | Pipeline | +-----------------+----------------------------------------------------------------------+----------------+------+---------+----------------+------------------------+-----------+---------------------+ | +ProduceResults | person | 1 | 1 | 0 | | | | | | | +----------------------------------------------------------------------+----------------+------+---------+----------------+ | | | | +NodeIndexSeek | RANGE INDEX person:Person(firstname) WHERE firstname = $autostring_0 | 1 | 1 | 2 | 120 | 2/1 | 0.292 | Fused in Pipeline 0 | +-----------------+----------------------------------------------------------------------+----------------+------+---------+----------------+------------------------+-----------+---------------------+ Total database accesses: 2, total allocated memory: 184
Equality check using WHERE
(composite index)
A query containing equality comparisons for all the properties of a composite index will automatically be backed by the same index. However, the query does not need to have equality on all properties. It can have ranges and existence predicates as well. But in these cases rewrites might happen depending on which properties have which predicates, see composite index limitations.
PROFILE
MATCH (n:Person)
WHERE n.age = 35 AND n.country = 'UK'
RETURN n
However, the query MATCH (n:Person) WHERE n.age = 35 RETURN n
will not be backed by the composite index, as the query does not contain a predicate on the country
property.
It will only be backed by an index on the Person
label and age
property defined thus: :Person(age)
; i.e. a single-property index.
Planner COST Runtime PIPELINED Runtime version 5.5 Batch size 128 +-----------------+----+---------------------------------------------------------------------------------------+----------------+------+---------+----------------+------------------------+-----------+---------------------+ | Operator | Id | Details | Estimated Rows | Rows | DB Hits | Memory (Bytes) | Page Cache Hits/Misses | Time (ms) | Pipeline | +-----------------+----+---------------------------------------------------------------------------------------+----------------+------+---------+----------------+------------------------+-----------+---------------------+ | +ProduceResults | 0 | n | 0 | 3 | 24 | | | | | | | +----+---------------------------------------------------------------------------------------+----------------+------+---------+----------------+ | | | | +NodeIndexSeek | 1 | RANGE INDEX n:Person(age, country) WHERE age = $autoint_0 AND country = $autostring_1 | 0 | 3 | 4 | 120 | 3/0 | 0.476 | Fused in Pipeline 0 | +-----------------+----+---------------------------------------------------------------------------------------+----------------+------+---------+----------------+------------------------+-----------+---------------------+ Total database accesses: 28, total allocated memory: 184
Range comparisons using WHERE
(single-property index)
Single-property indexes are also automatically used for inequality (range) comparisons of an indexed property in the WHERE
clause.
PROFILE
MATCH (friend)<-[r:KNOWS]-(person)
WHERE r.since < 2011
RETURN friend, person
Planner COST Runtime PIPELINED Runtime version 5.14 Batch size 128 +---------------------------------------+--------------------------------------------------------------------------+----------------+------+---------+----------------+------------------------+-----------+---------------------+ | Operator | Details | Estimated Rows | Rows | DB Hits | Memory (Bytes) | Page Cache Hits/Misses | Time (ms) | Pipeline | +---------------------------------------+--------------------------------------------------------------------------+----------------+------+---------+----------------+------------------------+-----------+---------------------+ | +ProduceResults | friend, person | 1 | 1 | 0 | | | | | | | +--------------------------------------------------------------------------+----------------+------+---------+----------------+ | | | | +DirectedRelationshipIndexSeekByRange | RANGE INDEX (person)-[r:KNOWS(since)]->(friend) WHERE since < $autoint_0 | 1 | 1 | 2 | 120 | 2/1 | 0.943 | Fused in Pipeline 0 | +---------------------------------------+--------------------------------------------------------------------------+----------------+------+---------+----------------+------------------------+-----------+---------------------+ Total database accesses: 2, total allocated memory: 184
Range comparisons using WHERE
(composite index)
Composite indexes are also automatically used for inequality (range) comparisons of indexed properties in the WHERE
clause.
Equality or list membership check predicates may precede the range predicate.
However, predicates after the range predicate may be rewritten as an existence check predicate and a filter as described in composite index limitations.
PROFILE
MATCH ()-[r:KNOWS]-()
WHERE r.since < 2011 AND r.lastMet > 2019
RETURN r.since
Planner COST Runtime PIPELINED Runtime version 5.14 Batch size 128 +----------------------------------+-----------------------------------------------------------------------------------------------------+----------------+------+---------+----------------+------------------------+-----------+---------------------+ | Operator | Details | Estimated Rows | Rows | DB Hits | Memory (Bytes) | Page Cache Hits/Misses | Time (ms) | Pipeline | +----------------------------------+-----------------------------------------------------------------------------------------------------+----------------+------+---------+----------------+------------------------+-----------+---------------------+ | +ProduceResults | `r.since` | 2 | 2 | 0 | | | | | | | +-----------------------------------------------------------------------------------------------------+----------------+------+---------+----------------+ | | | | +Projection | cache[r.since] AS `r.since` | 2 | 2 | 0 | | | | | | | +-----------------------------------------------------------------------------------------------------+----------------+------+---------+----------------+ | | | | +Filter | cache[r.lastMet] > $autoint_1 | 2 | 2 | 0 | | | | | | | +-----------------------------------------------------------------------------------------------------+----------------+------+---------+----------------+ | | | | +UndirectedRelationshipIndexSeek | RANGE INDEX (anon_0)-[r:KNOWS(since, lastMet)]-(anon_1) WHERE since < $autoint_0 AND lastMet IS NOT | 2 | 2 | 2 | 120 | 1/1 | 0.525 | Fused in Pipeline 0 | | | NULL, cache[r.since], cache[r.lastMet] | | | | | | | | +----------------------------------+-----------------------------------------------------------------------------------------------------+----------------+------+---------+----------------+------------------------+-----------+---------------------+ Total database accesses: 2, total allocated memory: 184
Multiple range comparisons using WHERE
(single-property index)
When the WHERE
clause contains multiple inequality (range) comparisons for the same property, these can be combined in a single index range seek.
PROFILE
MATCH (person:Person)
WHERE 10000 < person.highScore < 20000
RETURN person
Planner COST Runtime PIPELINED Runtime version 5.14 Batch size 128 +-----------------------+----------------------------------------------------------------------------------------------+----------------+------+---------+----------------+------------------------+-----------+---------------------+ | Operator | Details | Estimated Rows | Rows | DB Hits | Memory (Bytes) | Page Cache Hits/Misses | Time (ms) | Pipeline | +-----------------------+----------------------------------------------------------------------------------------------+----------------+------+---------+----------------+------------------------+-----------+---------------------+ | +ProduceResults | person | 1 | 1 | 0 | | | | | | | +----------------------------------------------------------------------------------------------+----------------+------+---------+----------------+ | | | | +NodeIndexSeekByRange | RANGE INDEX person:Person(highScore) WHERE highScore > $autoint_0 AND highScore < $autoint_1 | 1 | 1 | 2 | 120 | 2/1 | 0.286 | Fused in Pipeline 0 | +-----------------------+----------------------------------------------------------------------------------------------+----------------+------+---------+----------------+------------------------+-----------+---------------------+ Total database accesses: 2, total allocated memory: 184
Multiple range comparisons using WHERE
(composite index)
When the WHERE
clause contains multiple inequality (range) comparisons for the same property, these can be combined in a single index range seek.
That single range seek created in the following query will then use the composite index Person(highScore, name)
if it exists.
PROFILE
MATCH (person:Person)
WHERE 10000 < person.highScore < 20000 AND person.name IS NOT NULL
RETURN person
Planner COST Runtime PIPELINED Runtime version 5.14 Batch size 128 +-----------------+------------------------------------------------------------------------------------------------------+----------------+------+---------+----------------+------------------------+-----------+---------------------+ | Operator | Details | Estimated Rows | Rows | DB Hits | Memory (Bytes) | Page Cache Hits/Misses | Time (ms) | Pipeline | +-----------------+------------------------------------------------------------------------------------------------------+----------------+------+---------+----------------+------------------------+-----------+---------------------+ | +ProduceResults | person | 1 | 1 | 0 | | | | | | | +------------------------------------------------------------------------------------------------------+----------------+------+---------+----------------+ | | | | +NodeIndexSeek | RANGE INDEX person:Person(highScore, name) WHERE highScore > $autoint_0 AND highScore < $autoint_1 A | 1 | 1 | 2 | 120 | 2/1 | 4.498 | Fused in Pipeline 0 | | | ND name IS NOT NULL | | | | | | | | +-----------------+------------------------------------------------------------------------------------------------------+----------------+------+---------+----------------+------------------------+-----------+---------------------+ Total database accesses: 2, total allocated memory: 184
List membership check using IN
(single-property index)
The IN
predicate on r.since
in the following query will use the single-property index KNOWS(lastMetIn)
if it exists.
PROFILE
MATCH (person)-[r:KNOWS]->(friend)
WHERE r.lastMetIn IN ['Malmo', 'Stockholm']
RETURN person, friend
Planner COST Runtime PIPELINED Runtime version 5.14 Batch size 128 +--------------------------------+------------------------------------------------------------------------------------+----------------+------+---------+----------------+------------------------+-----------+---------------------+ | Operator | Details | Estimated Rows | Rows | DB Hits | Memory (Bytes) | Page Cache Hits/Misses | Time (ms) | Pipeline | +--------------------------------+------------------------------------------------------------------------------------+----------------+------+---------+----------------+------------------------+-----------+---------------------+ | +ProduceResults | person, friend | 1 | 1 | 0 | | | | | | | +------------------------------------------------------------------------------------+----------------+------+---------+----------------+ | | | | +DirectedRelationshipIndexSeek | RANGE INDEX (person)-[r:KNOWS(lastMetIn)]->(friend) WHERE lastMetIn IN $autolist_0 | 1 | 1 | 3 | 120 | 3/1 | 0.614 | Fused in Pipeline 0 | +--------------------------------+------------------------------------------------------------------------------------+----------------+------+---------+----------------+------------------------+-----------+---------------------+ Total database accesses: 3, total allocated memory: 184
List membership check using IN
(composite index)
The IN
predicates on r.since
and r.lastMet
in the following query will use the composite index KNOWS(since, lastMet)
if it exists.
PROFILE
MATCH (person)-[r:KNOWS]->(friend)
WHERE r.since IN [1992, 2017] AND r.lastMet IN [2002, 2021]
RETURN person, friend
Planner COST Runtime PIPELINED Runtime version 5.14 Batch size 128 +--------------------------------+------------------------------------------------------------------------------------------------------+----------------+------+---------+----------------+------------------------+-----------+---------------------+ | Operator | Details | Estimated Rows | Rows | DB Hits | Memory (Bytes) | Page Cache Hits/Misses | Time (ms) | Pipeline | +--------------------------------+------------------------------------------------------------------------------------------------------+----------------+------+---------+----------------+------------------------+-----------+---------------------+ | +ProduceResults | person, friend | 1 | 1 | 0 | | | | | | | +------------------------------------------------------------------------------------------------------+----------------+------+---------+----------------+ | | | | +DirectedRelationshipIndexSeek | RANGE INDEX (person)-[r:KNOWS(since, lastMet)]->(friend) WHERE since IN $autolist_0 AND lastMet IN $ | 1 | 1 | 5 | 120 | 5/1 | 1.864 | Fused in Pipeline 0 | | | autolist_1 | | | | | | | | +--------------------------------+------------------------------------------------------------------------------------------------------+----------------+------+---------+----------------+------------------------+-----------+---------------------+ Total database accesses: 5, total allocated memory: 184
Prefix search using STARTS WITH
(single-property index)
The STARTS WITH
predicate on person.firstname
in the following query will use the Person(firstname)
index, if it exists.
PROFILE
MATCH (person:Person)
WHERE person.firstname STARTS WITH 'And'
RETURN person
Planner COST Runtime PIPELINED Runtime version 5.14 Batch size 128 +-----------------------+--------------------------------------------------------------------------------+----------------+------+---------+----------------+------------------------+-----------+---------------------+ | Operator | Details | Estimated Rows | Rows | DB Hits | Memory (Bytes) | Page Cache Hits/Misses | Time (ms) | Pipeline | +-----------------------+--------------------------------------------------------------------------------+----------------+------+---------+----------------+------------------------+-----------+---------------------+ | +ProduceResults | person | 2 | 1 | 0 | | | | | | | +--------------------------------------------------------------------------------+----------------+------+---------+----------------+ | | | | +NodeIndexSeekByRange | RANGE INDEX person:Person(firstname) WHERE firstname STARTS WITH $autostring_0 | 2 | 1 | 2 | 120 | 3/0 | 0.387 | Fused in Pipeline 0 | +-----------------------+--------------------------------------------------------------------------------+----------------+------+---------+----------------+------------------------+-----------+---------------------+ Total database accesses: 2, total allocated memory: 184
Prefix search using STARTS WITH
(composite index)
The STARTS WITH
predicate on person.firstname
in the following query will use the Person(firstname,surname)
index, if it exists.
Any (non-existence check) predicate on person.surname
will be rewritten as existence check with a filter.
However, if the predicate on person.firstname
is a equality check then a STARTS WITH
on person.surname
would also use the index (without rewrites).
More information about how the rewriting works can be found in composite index limitations.
PROFILE
MATCH (person:Person)
WHERE person.firstname STARTS WITH 'And' AND person.surname IS NOT NULL
RETURN person
Planner COST Runtime PIPELINED Runtime version 5.14 Batch size 128 +-----------------+-----------------------------------------------------------------------------------------------------+----------------+------+---------+----------------+------------------------+-----------+---------------------+ | Operator | Details | Estimated Rows | Rows | DB Hits | Memory (Bytes) | Page Cache Hits/Misses | Time (ms) | Pipeline | +-----------------+-----------------------------------------------------------------------------------------------------+----------------+------+---------+----------------+------------------------+-----------+---------------------+ | +ProduceResults | person | 1 | 1 | 0 | | | | | | | +-----------------------------------------------------------------------------------------------------+----------------+------+---------+----------------+ | | | | +NodeIndexSeek | RANGE INDEX person:Person(firstname, surname) WHERE firstname STARTS WITH $autostring_0 AND surname | 1 | 1 | 2 | 120 | 3/0 | 0.534 | Fused in Pipeline 0 | | | IS NOT NULL | | | | | | | | +-----------------+-----------------------------------------------------------------------------------------------------+----------------+------+---------+----------------+------------------------+-----------+---------------------+ Total database accesses: 2, total allocated memory: 184
Suffix search using ENDS WITH
(single-property index)
The ENDS WITH
predicate on r.metIn
in the following query uses the KNOWS(metIn)
index, if it exists.
Text indexes are optimized for CONTAINS
and ENDS WITH
and they are the only indexes that can solve those predicates.
PROFILE
MATCH (person)-[r:KNOWS]->(friend)
WHERE r.metIn ENDS WITH 'mo'
RETURN person, friend
Planner COST Runtime PIPELINED Runtime version 5.5 Batch size 128 +----------------------------------------+----+------------------------------------------------------------------------------------+----------------+------+---------+----------------+------------------------+-----------+---------------------+ | Operator | Id | Details | Estimated Rows | Rows | DB Hits | Memory (Bytes) | Page Cache Hits/Misses | Time (ms) | Pipeline | +----------------------------------------+----+------------------------------------------------------------------------------------+----------------+------+---------+----------------+------------------------+-----------+---------------------+ | +ProduceResults | 0 | person, friend | 0 | 0 | 0 | | | | | | | +----+------------------------------------------------------------------------------------+----------------+------+---------+----------------+ | | | | +DirectedRelationshipIndexEndsWithScan | 1 | TEXT INDEX (person)-[r:KNOWS(metIn)]->(friend) WHERE metIn ENDS WITH $autostring_0 | 0 | 0 | 1 | 120 | 0/0 | 1.135 | Fused in Pipeline 0 | +----------------------------------------+----+------------------------------------------------------------------------------------+----------------+------+---------+----------------+------------------------+-----------+---------------------+ Total database accesses: 1, total allocated memory: 184
Text indexes only index String values and therefore do not find other values.
Suffix search using ENDS WITH
(composite index)
The ENDS WITH
predicate on r.metIn
in the following query uses the KNOWS(metIn, lastMetIn)
index, if it exists.
However, it is rewritten as existence check and a filter due to the index not supporting actual suffix searches for composite indexes, this is still faster than not using an index in the first place.
Any (non-existence check) predicate on KNOWS.lastMetIn
is also rewritten as existence check with a filter.
More information about how the rewriting works can be found in composite index limitations.
PROFILE
MATCH (person)-[r:KNOWS]->(friend)
WHERE r.metIn ENDS WITH 'mo' AND r.lastMetIn IS NOT NULL
RETURN person, friend
Planner COST Runtime PIPELINED Runtime version 5.14 Batch size 128 +--------------------------------+-----------------------------------------------------------------------------------------------------+----------------+------+---------+----------------+------------------------+-----------+---------------------+ | Operator | Details | Estimated Rows | Rows | DB Hits | Memory (Bytes) | Page Cache Hits/Misses | Time (ms) | Pipeline | +--------------------------------+-----------------------------------------------------------------------------------------------------+----------------+------+---------+----------------+------------------------+-----------+---------------------+ | +ProduceResults | person, friend | 0 | 1 | 0 | | | | | | | +-----------------------------------------------------------------------------------------------------+----------------+------+---------+----------------+ | | | | +Filter | cache[r.metIn] ENDS WITH $autostring_0 | 0 | 1 | 0 | | | | | | | +-----------------------------------------------------------------------------------------------------+----------------+------+---------+----------------+ | | | | +DirectedRelationshipIndexScan | RANGE INDEX (person)-[r:KNOWS(metIn, lastMetIn)]->(friend) WHERE metIn IS NOT NULL AND lastMetIn IS | 1 | 1 | 2 | 120 | 2/1 | 0.317 | Fused in Pipeline 0 | | | NOT NULL, cache[r.metIn] | | | | | | | | +--------------------------------+-----------------------------------------------------------------------------------------------------+----------------+------+---------+----------------+------------------------+-----------+---------------------+ Total database accesses: 2, total allocated memory: 184
Substring search using CONTAINS
(single-property index)
The CONTAINS
predicate on person.firstname
in the following query will use the Person(firstname)
index, if it exists.
Text indexes are optimized for CONTAINS
and ENDS WITH
and they are the only indexes that can solve those predicates.
Composite indexes are currently not able to support CONTAINS
.
PROFILE
MATCH (person:Person)
WHERE person.firstname CONTAINS 'h'
RETURN person
Text indexes only index String values and therefore do not find other values.
Substring search using CONTAINS
(composite index)
The CONTAINS
predicate on person.country
in the following query will use the Person(country,age)
index, if it exists.
However, it will be rewritten as existence check and a filter due to the index not supporting actual suffix searches for composite indexes, this is still faster than not using an index in the first place.
Any (non-existence check) predicate on person.age
will also be rewritten as existence check with a filter.
More information about how the rewriting works can be found in composite index limitations.
PROFILE
MATCH (person:Person)
WHERE person.country CONTAINS '300' AND person.age IS NOT NULL
RETURN person
Planner COST Runtime PIPELINED Runtime version 5.14 Batch size 128 +-----------------+------------------------------------------------------------------------------------------------------+----------------+------+---------+----------------+------------------------+-----------+---------------------+ | Operator | Details | Estimated Rows | Rows | DB Hits | Memory (Bytes) | Page Cache Hits/Misses | Time (ms) | Pipeline | +-----------------+------------------------------------------------------------------------------------------------------+----------------+------+---------+----------------+------------------------+-----------+---------------------+ | +ProduceResults | person | 15 | 1 | 0 | | | | | | | +------------------------------------------------------------------------------------------------------+----------------+------+---------+----------------+ | | | | +Filter | cache[person.country] CONTAINS $autostring_0 | 15 | 1 | 0 | | | | | | | +------------------------------------------------------------------------------------------------------+----------------+------+---------+----------------+ | | | | +NodeIndexScan | RANGE INDEX person:Person(country, age) WHERE country IS NOT NULL AND age IS NOT NULL, cache[person. | 303 | 303 | 304 | 120 | 5/0 | 2.309 | Fused in Pipeline 0 | | | country] | | | | | | | | +-----------------+------------------------------------------------------------------------------------------------------+----------------+------+---------+----------------+------------------------+-----------+---------------------+ Total database accesses: 304, total allocated memory: 184
Existence check using IS NOT NULL
(single-property index)
The r.since IS NOT NULL
predicate in the following query uses the KNOWS(since)
index, if it exists.
PROFILE
MATCH (person)-[r:KNOWS]->(friend)
WHERE r.since IS NOT NULL
RETURN person, friend
Planner COST Runtime PIPELINED Runtime version 5.14 Batch size 128 +--------------------------------+-------------------------------------------------------------------------+----------------+------+---------+----------------+------------------------+-----------+---------------------+ | Operator | Details | Estimated Rows | Rows | DB Hits | Memory (Bytes) | Page Cache Hits/Misses | Time (ms) | Pipeline | +--------------------------------+-------------------------------------------------------------------------+----------------+------+---------+----------------+------------------------+-----------+---------------------+ | +ProduceResults | person, friend | 1 | 1 | 0 | | | | | | | +-------------------------------------------------------------------------+----------------+------+---------+----------------+ | | | | +DirectedRelationshipIndexScan | RANGE INDEX (person)-[r:KNOWS(since)]->(friend) WHERE since IS NOT NULL | 1 | 1 | 2 | 120 | 2/1 | 1.046 | Fused in Pipeline 0 | +--------------------------------+-------------------------------------------------------------------------+----------------+------+---------+----------------+------------------------+-----------+---------------------+ Total database accesses: 2, total allocated memory: 184
Existence check using IS NOT NULL
(composite index)
The p.firstname IS NOT NULL
and p.surname IS NOT NULL
predicates in the following query will use the Person(firstname,surname)
index, if it exists.
Any (non-existence check) predicate on person.surname
will be rewritten as existence check with a filter.
PROFILE
MATCH (p:Person)
WHERE p.firstname IS NOT NULL AND p.surname IS NOT NULL
RETURN p
Planner COST Runtime PIPELINED Runtime version 5.14 Batch size 128 +-----------------+----------------------------------------------------------------------------------------------+----------------+------+---------+----------------+------------------------+-----------+---------------------+ | Operator | Details | Estimated Rows | Rows | DB Hits | Memory (Bytes) | Page Cache Hits/Misses | Time (ms) | Pipeline | +-----------------+----------------------------------------------------------------------------------------------+----------------+------+---------+----------------+------------------------+-----------+---------------------+ | +ProduceResults | p | 1 | 2 | 0 | | | | | | | +----------------------------------------------------------------------------------------------+----------------+------+---------+----------------+ | | | | +NodeIndexScan | RANGE INDEX p:Person(firstname, surname) WHERE firstname IS NOT NULL AND surname IS NOT NULL | 1 | 2 | 3 | 120 | 2/1 | 0.310 | Fused in Pipeline 0 | +-----------------+----------------------------------------------------------------------------------------------+----------------+------+---------+----------------+------------------------+-----------+---------------------+ Total database accesses: 3, total allocated memory: 184
Spatial distance searches (single-property index)
If a property with point values is indexed, the index is used for spatial distance searches as well as for range queries.
PROFILE
MATCH ()-[r:KNOWS]->()
WHERE point.distance(r.lastMetPoint, point({x: 1, y: 2})) < 2
RETURN r.lastMetPoint
Planner COST Runtime PIPELINED Runtime version 5.14 Batch size 128 +---------------------------------------+------------------------------------------------------------------------------------------------------+----------------+------+---------+----------------+------------------------+-----------+---------------------+ | Operator | Details | Estimated Rows | Rows | DB Hits | Memory (Bytes) | Page Cache Hits/Misses | Time (ms) | Pipeline | +---------------------------------------+------------------------------------------------------------------------------------------------------+----------------+------+---------+----------------+------------------------+-----------+---------------------+ | +ProduceResults | `r.lastMetPoint` | 13 | 9 | 0 | | | | | | | +------------------------------------------------------------------------------------------------------+----------------+------+---------+----------------+ | | | | +Projection | cache[r.lastMetPoint] AS `r.lastMetPoint` | 13 | 9 | 0 | | | | | | | +------------------------------------------------------------------------------------------------------+----------------+------+---------+----------------+ | | | | +Filter | point.distance(cache[r.lastMetPoint], point({x: $autoint_0, y: $autoint_1})) < $autoint_2 | 13 | 9 | 0 | | | | | | | +------------------------------------------------------------------------------------------------------+----------------+------+---------+----------------+ | | | | +DirectedRelationshipIndexSeekByRange | POINT INDEX (anon_0)-[r:KNOWS(lastMetPoint)]->(anon_1) WHERE point.distance(lastMetPoint, point($aut | 13 | 9 | 10 | 120 | 5/3 | 1.417 | Fused in Pipeline 0 | | | oint_0, $autoint_1)) < $autoint_2, cache[r.lastMetPoint] | | | | | | | | +---------------------------------------+------------------------------------------------------------------------------------------------------+----------------+------+---------+----------------+------------------------+-----------+---------------------+ Total database accesses: 10, total allocated memory: 184
Spatial bounding box searches (single-property index)
The ability to do index seeks on bounded ranges works even with the 2D and 3D spatial POINT
types.
PROFILE
MATCH (person:Person)
WHERE point.withinBBox(person.location, point({x: 1.2, y: 5.4}), point({x: 1.3, y: 5.5}))
RETURN person.firstname
Planner COST Runtime PIPELINED Runtime version 5.14 Batch size 128 +-----------------------+------------------------------------------------------------------------------------------------------+----------------+------+---------+----------------+------------------------+-----------+---------------------+ | Operator | Details | Estimated Rows | Rows | DB Hits | Memory (Bytes) | Page Cache Hits/Misses | Time (ms) | Pipeline | +-----------------------+------------------------------------------------------------------------------------------------------+----------------+------+---------+----------------+------------------------+-----------+---------------------+ | +ProduceResults | `person.firstname` | 0 | 1 | 0 | | | | | | | +------------------------------------------------------------------------------------------------------+----------------+------+---------+----------------+ | | | | +Projection | person.firstname AS `person.firstname` | 0 | 1 | 2 | | | | | | | +------------------------------------------------------------------------------------------------------+----------------+------+---------+----------------+ | | | | +NodeIndexSeekByRange | POINT INDEX person:Person(location) WHERE point.withinBBox(location, point($autodouble_0, $autodoubl | 0 | 1 | 2 | 120 | 6/0 | 7.910 | Fused in Pipeline 0 | | | e_1), point($autodouble_2, $autodouble_3)) | | | | | | | | +-----------------------+------------------------------------------------------------------------------------------------------+----------------+------+---------+----------------+------------------------+-----------+---------------------+ Total database accesses: 4, total allocated memory: 184
Was this page helpful?