知识库

将显式 Lucene 索引迁移到原生 Schema 索引

鉴于一些客户仍在使用旧版 Neo4j 版本,这些版本使用旧版/显式索引,我们将在本文档中讨论一些关于在升级到 Neo4j 4.x 版本时如何将这些索引转换为原生 Schema 索引的要点,因为旧版/显式索引从 3.5.x 版本开始已弃用,并在 4.x 版本中完全移除。

背景信息:在 Neo4j 3.2(2017 年发布)之前,旧版/显式索引是唯一可用的索引类型。这些早期版本的索引是在幕后通过 Lucene 实现的,通常会导致写入性能显著下降,因此需要用 3.3 及更高版本中提供的原生 Schema 索引替换它们。

除了性能方面,同样重要的是,在通过 Java API(以及从 3.3.x 版本开始的存储过程)添加/删除/更新节点/关系/属性时,还必须显式/手动地维护显式索引的最新状态,因此它们被称为显式索引。

相反,原生索引会自动维护,因此无需任何此类手动步骤和/或额外代码来保持其最新状态。

从实现的角度来看,这些旧版索引没有列出很多 Schema 详情,除了索引创建时原始作者选择的命名约定之外,没有太多有用的线索可以帮助以脚本方式实现将这些索引迁移到 Neo4j 的原生 Schema 索引的自动化过程。

因此,任何索引迁移都必须逐个索引进行,并且可能涉及一定程度的猜测。这意味着您必须查看 API 代码并评估哪些节点或关系属性被索引,并相应地在这些属性上实现补充 Schema 索引,或者查看 Cypher 查询并想出支持的索引来优化其执行。

总体而言,在较高层次上,以下是推荐的方法

1) 获取现有版本上所有索引的列表

  • 例如:在 3.x 中,您可以使用 "call db.index.explicit.list();",它将显示索引名称及其类型(可以是“精确”或“全文”),以及它是节点索引还是关系索引,这将帮助您确定要构建哪种类型的 Schema 索引。

2) 接下来查看您的 Cypher 和/或 Java API 代码,并将其相应地转换为 4.x+ 格式。

  • 例如,将 “start” 语句转换为等效的 Match 语句。示例

    • START n=node:myExplicitIndexYear("myid:1234567") RETURN n;

    • 上述查询将更改为以下内容

    • MATCH (n:Person{myid:1234567}) RETURN n;

3) 实现您需要支持并优化步骤 2 中关联/转换的查询执行的等效 Schema 索引。

  • 例如: CREATE INDEX index_name FOR (n:Person) ON (n.myid);

  • 更具体地说

    • 检查所有 Cypher 查询以了解它们实际写入该索引的内容,并推导出 create index(对于整数、字符串、日期、地理空间等)和/或 call db.index.fulltext.createIndexForNodes(FTS)语句。

    • 检查所有读取该索引的操作,并采用语句以使用新索引(这意味着启用查询日志记录以捕获生成的 Cypher 查询及其性能属性,例如执行时间与 IO 与内存与 CPU,以标记最长运行的查询以逐个进行故障排除 - 并且很有可能您的查询模式彼此相似,因此,一旦一个查询修复,它也将类似地帮助其他类似的查询)。

4) 完成所有显式索引后,关闭数据库并移动/删除关联的索引子文件夹(例如 /data/databases/graph.db/index)。

请注意,如果碰巧将旧版索引转换为等效的 Schema 索引后,使用旧版索引的搜索查询返回的行数意外(与 Schema 索引相比),则这可能表示 Schema 索引可能定义不正确(在错误的标签:属性上),或者旧版索引未更新(请记住,旧版索引必须手动保持最新,因此,例如,如果您的 Java API 代码中没有适当的错误处理,则完全有可能由于任何原因(例如服务器重启等)导致回滚时事务未正确重试和应用提交)。这是一个 3.x 示例,演示了这种情况

// Create New Nodes
//
CREATE (p:Person {name:'Steve_1',year:1990});
CREATE (p:Person {name:'Steve_2',year:1990});
CREATE (p:Person {name:'Steve_3',year:1990});


// Create Two Explicit Indexes(also called Legacy/Manual/Lucene Indexes)
//
CALL db.index.explicit.forNodes('myExplicitIndexFTS', {type: 'fulltext', provider: 'lucene'});
CALL db.index.explicit.forNodes('myExplicitIndexYear', {type: 'exact', provider: 'lucene'});

// List Explicit Indexes
//
call db.index.explicit.list;

// Index nodes with name property
//
MATCH (p:Person{name:'Steve_1'}) call db.index.explicit.addNode('myExplicitIndexFTS', p, 'name', 'Steve_1') yield success return count(*);
MATCH (p:Person{name:'Steve_2'}) call db.index.explicit.addNode('myExplicitIndexFTS', p, 'name', 'Steve_2') yield success return count(*);

// Index nodes with "birth year" property
//
match (p:Person{name:'Steve_1'})
with p
call db.index.explicit.addNode("myExplicitIndexYear",p,"year",p.year) yield success return count(*);

// Search for persons with matching name VE, which will only return Steve_1, and Steve_2
//
CALL db.index.explicit.searchNodes('myExplicitIndexFTS','name:*VE*');

// Search for persons with year=1990 which will only return Steve_1
//
CALL db.index.explicit.searchNodes('myExplicitIndexYear','year:1990');

// Search for persons with year=1990 using explicit index which will only return Steve_1
//
start n=node:myExplicitIndexYear("year:1990") return n;

// Convert "start" to equivalent Match statement, and this statement returns all 3 rows corresponding to year=1990 (and of course ideally, you would want to create an index on :Person(
year) or :Person(name) for best performance when creating equivalent native schema indexes on these two properties.
match (n:Person{year:1990}) return n;

附录