3.5.1. Indexes

This section explains how to work with indexes in Neo4j and Cypher.

3.5.1.1. Introduction

A database index is a redundant copy of information in the database for the purpose of making retrieving said data more efficient. This comes at the cost of additional storage space and slower writes, so deciding what to index and what not to index is an important and often non-trivial task.

Cypher enables the creation of indexes on one or more properties for all nodes that have a given label:

  • An index that is created on a single property for any given label is called a single-property index.
  • An index that is created on more than one property for any given label is called a composite index. Differences in the usage patterns between composite and single-property indexes are detailed in the examples below.

Once an index has been created, it will automatically be managed and kept up to date by the database when the graph is changed. Neo4j will automatically pick up and start using the index once it has been created and brought online.

3.5.1.2. Create a single-property index

An index on a single property for all nodes that have a particular label can be created with CREATE INDEX ON :Label(property). Note that the index is not immediately available, but will be created in the background.

Query. 

CREATE INDEX ON :Person(firstname)

Result. 

+--------------------------------------------+
| No data returned, and nothing was changed. |
+--------------------------------------------+

3.5.1.3. Create a composite index

An index on multiple properties for all nodes that have a particular label — i.e. a composite index — can be created with CREATE INDEX ON :Label(prop1, …​, propN). Only nodes labeled with the specified label and which contain all the properties in the index definition will be added to the index.

The following statement will create a composite index on all nodes labeled with Person and which have both a firstname and surname property:

CREATE INDEX ON :Person(firstname, surname)

Now, assume the following query is run:

CREATE (a:Person {firstname: 'Bill', surname: 'Johnson', age: 34}),
(b:Person {firstname: 'Sue', age: 39}

Node a has both a firstname and a surname property, and so it will be added to the composite index. However, as node b has no surname property, it will not be added to the composite index.

Note that the composite index is not immediately available, but will be created in the background.

3.5.1.4. Drop a single-property index

An index on all nodes that have a label and single property combination can be dropped with DROP INDEX ON :Label(property).

Query. 

DROP INDEX ON :Person(firstname)

Result. 

+-------------------+
| No data returned. |
+-------------------+
Indexes removed: 1

3.5.1.5. Drop a composite index

A composite index on all nodes that have a label and multiple property combination can be dropped with DROP INDEX ON :Label(prop1, …​, propN).

The following statement will drop a composite index on all nodes labeled with Person and which have both a firstname and surname property:

DROP INDEX ON :Person(firstname, surname)

3.5.1.6. Use index

There is usually no need to specify which indexes to use in a query, Cypher will figure that out by itself. For example the query below will use the Person(firstname) index, if it exists. If you want Cypher to use specific indexes, you can enforce it using hints. See Section 3.6.4, “Planner hints and the USING keyword”.

Query. 

MATCH (person:Person { firstname: 'Andres' })
RETURN person

Query Plan. 

+-----------------+----------------+------+---------+-----------------+-------------------+-----------+--------------------+
| Operator        | Estimated Rows | Rows | DB Hits | Page Cache Hits | Page Cache Misses | Variables | Other              |
+-----------------+----------------+------+---------+-----------------+-------------------+-----------+--------------------+
| +ProduceResults |              1 |    1 |       0 |               0 |                 0 | person    |                    |
| |               +----------------+------+---------+-----------------+-------------------+-----------+--------------------+
| +NodeIndexSeek  |              1 |    1 |       2 |               1 |                 0 | person    | :Person(firstname) |
+-----------------+----------------+------+---------+-----------------+-------------------+-----------+--------------------+

Total database accesses: 2

3.5.1.7. Use a single-property index with WHERE using equality

A query containing equality comparisons of a single indexed property in the WHERE clause is backed automatically by the index. If you want Cypher to use specific indexes, you can enforce it using hints. See Section 3.6.4, “Planner hints and the USING keyword”.

Query. 

MATCH (person:Person)
WHERE person.firstname = 'Andres'
RETURN person

Query Plan. 

+-----------------+----------------+------+---------+-----------------+-------------------+-----------+--------------------+
| Operator        | Estimated Rows | Rows | DB Hits | Page Cache Hits | Page Cache Misses | Variables | Other              |
+-----------------+----------------+------+---------+-----------------+-------------------+-----------+--------------------+
| +ProduceResults |              1 |    1 |       0 |               0 |                 0 | person    |                    |
| |               +----------------+------+---------+-----------------+-------------------+-----------+--------------------+
| +NodeIndexSeek  |              1 |    1 |       2 |               1 |                 0 | person    | :Person(firstname) |
+-----------------+----------------+------+---------+-----------------+-------------------+-----------+--------------------+

Total database accesses: 2

3.5.1.8. Use a composite index with WHERE using equality

A query containing equality comparisons for all the properties of a composite index will automatically be backed by the same index.

For instance, assume the following composite index has been created:

CREATE INDEX ON :Person(firstname, surname)

The following query will use the composite index:

MATCH (n:Person)
WHERE n.firstname = 'Bill' AND n.surname = 'Johnson'
RETURN n

However, this query will not be backed by the composite index, as the query does not contain an equality predicate on the surname property:

MATCH (n:Person)
WHERE n.firstname = 'Bill'
RETURN n

The query above will only be backed by an index on the Person label and firstname property defined thus: :Person(firstname); i.e. a single-property index.

Moreover, unlike single-property indexes, composite indexes currently do not support queries containing the following types of predicates on properties in the index:

  • Existence: exists(n.prop)
  • Range: n.prop > value
  • STARTS WITH
  • ENDS WITH
  • CONTAINS

Therefore, the following queries will not be able to be backed by the composite index defined earlier:

MATCH (n:Person)
WHERE n.firstname = 'Bill' AND exists(n.surname)
RETURN n
MATCH (n:Person)
WHERE n.firstname = 'Bill' AND n.surname STARTS WITH 'Jo'
RETURN n

If you want Cypher to use specific indexes, you can enforce it using hints. See Section 3.6.4, “Planner hints and the USING keyword”.

3.5.1.9. Use index with WHERE using range comparisons

Single-property indexes are also automatically used for inequality (range) comparisons of an indexed property in the WHERE clause. Composite indexes are currently not able to support range comparisons. If you want Cypher to use specific indexes, you can enforce it using hints. See Section 3.6.4, “Planner hints and the USING keyword”.

Query. 

MATCH (person:Person)
WHERE person.firstname > 'B'
RETURN person

Query Plan. 

+-----------------------+----------------+------+---------+-----------------+-------------------+-----------+--------------------------------------+
| Operator              | Estimated Rows | Rows | DB Hits | Page Cache Hits | Page Cache Misses | Variables | Other                                |
+-----------------------+----------------+------+---------+-----------------+-------------------+-----------+--------------------------------------+
| +ProduceResults       |             10 |    1 |       0 |               0 |                 0 | person    |                                      |
| |                     +----------------+------+---------+-----------------+-------------------+-----------+--------------------------------------+
| +NodeIndexSeekByRange |             10 |    1 |       2 |               1 |                 0 | person    | :Person(firstname) > {  AUTOSTRING0} |
+-----------------------+----------------+------+---------+-----------------+-------------------+-----------+--------------------------------------+

Total database accesses: 2

3.5.1.10. Use index with IN

The IN predicate on person.firstname in the following query will use the Person(firstname) index, if it exists. If you want Cypher to use specific indexes, you can enforce it using hints. See Section 3.6.4, “Planner hints and the USING keyword”.

Query. 

MATCH (person:Person)
WHERE person.firstname IN ['Andres', 'Mark']
RETURN person

Query Plan. 

+-----------------+----------------+------+---------+-----------------+-------------------+-----------+--------------------+
| Operator        | Estimated Rows | Rows | DB Hits | Page Cache Hits | Page Cache Misses | Variables | Other              |
+-----------------+----------------+------+---------+-----------------+-------------------+-----------+--------------------+
| +ProduceResults |             24 |    2 |       0 |               0 |                 0 | person    |                    |
| |               +----------------+------+---------+-----------------+-------------------+-----------+--------------------+
| +NodeIndexSeek  |             24 |    2 |       4 |               2 |                 0 | person    | :Person(firstname) |
+-----------------+----------------+------+---------+-----------------+-------------------+-----------+--------------------+

Total database accesses: 4

3.5.1.11. Use index with STARTS WITH

The STARTS WITH predicate on person.firstname in the following query will use the Person(firstname) index, if it exists. Composite indexes are currently not able to support STARTS WITH, ENDS WITH and CONTAINS.

Query. 

MATCH (person:Person)
WHERE person.firstname STARTS WITH 'And'
RETURN person

Query Plan. 

+-----------------------+----------------+------+---------+-----------------+-------------------+-----------+------------------------------------------------+
| Operator              | Estimated Rows | Rows | DB Hits | Page Cache Hits | Page Cache Misses | Variables | Other                                          |
+-----------------------+----------------+------+---------+-----------------+-------------------+-----------+------------------------------------------------+
| +ProduceResults       |             26 |    1 |       0 |               0 |                 0 | person    |                                                |
| |                     +----------------+------+---------+-----------------+-------------------+-----------+------------------------------------------------+
| +NodeIndexSeekByRange |             26 |    1 |       2 |               1 |                 0 | person    | :Person(firstname STARTS WITH {  AUTOSTRING0}) |
+-----------------------+----------------+------+---------+-----------------+-------------------+-----------+------------------------------------------------+

Total database accesses: 2

3.5.1.12. Use index when checking for the existence of a property

The exists(p.firstname) predicate in the following query will use the Person(firstname) index, if it exists. Composite indexes are currently not able to support the exists predicate.

Query. 

MATCH (p:Person)
WHERE exists(p.firstname)
RETURN p

Query Plan. 

+-----------------+----------------+------+---------+-----------------+-------------------+-----------+--------------------+
| Operator        | Estimated Rows | Rows | DB Hits | Page Cache Hits | Page Cache Misses | Variables | Other              |
+-----------------+----------------+------+---------+-----------------+-------------------+-----------+--------------------+
| +ProduceResults |              2 |    2 |       0 |               1 |                 0 | p         |                    |
| |               +----------------+------+---------+-----------------+-------------------+-----------+--------------------+
| +NodeIndexScan  |              2 |    2 |       3 |               2 |                 0 | p         | :Person(firstname) |
+-----------------+----------------+------+---------+-----------------+-------------------+-----------+--------------------+

Total database accesses: 3