【图数据库内核】标签、类型与属性索引:起点过滤器,不是遍历引擎
内容提要
本文介绍Neo4j 5的索引与约束机制。索引分四类:LOOKUP(标签/类型)、RANGE(属性范围)、TEXT(文本子串)、POINT(空间),用于加速查询起点选择,但不解决深度遍历爆炸问题。约束(唯一、存在、类型、KEY)保证完整性,但会带来写放大。过索引增加空间和写入成本,需谨慎设计。
延伸解读
索引的边界:起点过滤,而非遍历加速
本文明确指出,索引只负责快速找到候选起点,不解决深度遍历的爆炸问题。即使索引高效地选出50万个起点,每个点再扩展20个邻居,一层就会产生百万级中间结果。因此,优化查询时不能只依赖索引,还需关注谓词收紧、变长路径限制和建模调整。
低基数属性上的索引可能白费
手册建议优先为高基数属性建索引。在布尔值或状态枚举等低基数属性上,索引可能选出大量起点,计划看似用了NodeIndexSeek,实际效果接近标签扫描。此时索引的存储和写入成本几乎白付,甚至拖慢写路径。
复合索引的属性顺序至关重要
复合索引要求查询谓词覆盖索引定义的全部属性才能使用。若创建(a,b,c)而查询只提供c,索引可能完全用不上。错误顺序会导致“建了却不见seek”,因此需用EXPLAIN/PROFILE核对算子,确保索引真正生效。
过索引的代价:空间、写入与错误入口
索引是主数据的二级拷贝,空间上近似再占一份被索引数据,同时每次写入都要更新索引,拖慢写路径。手册推荐用SHOW INDEXES的lastRead/readCount等列识别闲置索引并删除,避免过度索引。
Q&A
Neo4j 5 中有哪些类型的索引?它们分别用于什么场景?
Neo4j 5 中有四类搜索性能索引:LOOKUP(标签/类型索引)、RANGE(属性范围索引)、TEXT(文本子串索引)和 POINT(空间索引)。LOOKUP 用于按标签或关系类型快速找到候选实体;RANGE 用于等值、范围、前缀匹配等大多数谓词;TEXT 用于字符串的 CONTAINS 和 ENDS WITH 匹配;POINT 用于距离和边界框等空间谓词。
删除 Neo4j 的 token lookup 索引会有什么后果?
删除 token lookup 索引会导致严重性能退化,因为查询计划可能退化为全库扫描(AllNodesScan),先读全库节点再过滤。此外,token lookup 索引还帮助其他索引的填充,删除后会影响整体查询性能。
Neo4j 的 TEXT 索引和 RANGE 索引在字符串查询上如何分工?
对于同一字符串属性,可以同时创建 RANGE 和 TEXT 索引。规划器对 CONTAINS 和 ENDS WITH 操作倾向于使用 TEXT 索引,而对精确匹配、前缀匹配等操作倾向于使用 RANGE 索引。TEXT 索引使用 trigram 切分,支持子串匹配,但模糊搜索和分词检索应使用全文索引(full-text)。
Neo4j 的唯一性约束与关系数据库的 UNIQUE 约束有何不同?
Neo4j 的唯一性约束默认允许缺少该属性的实体存在,即未具备约束属性的实体不受唯一性约束。这与关系数据库的 NOT NULL + UNIQUE 不同。如果需要“必须有且唯一”,应使用 NODE KEY 或 RELATIONSHIP KEY 约束(企业版),或组合唯一约束和存在性约束。
为什么在低基数属性上创建索引可能不是好主意?
低基数属性(如布尔值、状态枚举)上的索引可能选出巨大的起点集合,导致查询计划看似使用了索引,但实际上接近标签扫描,性能提升有限。同时,索引会占用存储空间并拖慢写入,因此在这种情况下索引的代价可能白付。
如何识别并处理过索引问题?
过索引会增加存储空间和写入成本。可以使用 SHOW INDEXES 命令(5.8+)查看索引的 lastRead、readCount 和 trackedSince 字段,找出闲置索引,然后使用 DROP INDEX 删除它们。手册推荐通过实验对比有无索引的性能,而不是凭感觉堆索引。
Neo4j 的索引能解决深度遍历爆炸问题吗?
不能。索引只用于加速查询起点的选择,减少候选实体集合,但无法替代深度遍历(expand)操作。如果起点集合仍然巨大,后续的 expand 操作仍可能导致性能问题。因此,需要结合查询优化、建模调整等手段来控制遍历深度。