索引策略:标签索引、属性索引、全文索引
讲索引之前,我先说个背景。药物图谱里最典型的场景是什么?查找某个药物的靶点、通路,或者检索一片文献里提到的化合物。这些查询动辄遍历几百万个节点和边。没有索引,就是全表扫描——慢到怀疑人生。
我最早做药物图谱时,图数据库用的是 Neo4j。当时有个同事说:“图数据库不是天生快的吗?” 嗯,这种误解我见过太多次了。图数据库快的前提是——索引用得对。今天我就把几种常用索引策略掰开揉碎了讲。
标签索引
标签(Label)是图数据库里给节点分类的基础手段。比如:Drug、:Target、:Disease。标签索引说白了就是让数据库能快速找到“某一类”节点。
为什么一定要建标签索引?举个例子:
MATCH (d:Drug) WHERE d.name = 'Ibuprofen' RETURN d
如果没有标签索引,数据库得遍历所有节点,找到标签为 Drug 的,再过滤 name。这就像在图书馆里找一本特定书名,但管理员不知道书在哪个区域——只能一本一本翻。
标签索引的创建方式很简单。以 Neo4j 为例:
CREATE INDEX FOR (d:Drug) ON (d.name)
我个人习惯:所有业务标签都建索引。哪怕是一个查询频率很低的标签,也值得建。为什么?因为一旦数据量上来(比如几亿节点),遍历一次的代价远超索引维护开销。
:Drug、:Target、:Gene 这类高基数的标签必建索引;:Log、:Temp 这类临时标签则按需创建。
属性索引
属性索引是针对节点或边的具体字段建的。常见的有单属性索引、复合索引、唯一索引。咱们一个个说。
单属性索引
就是针对一个字段建立索引。比如按药物的化学名称查找:
CREATE INDEX drug_name_index FOR (d:Drug) ON (d.chembl_id)
这里要注意:索引的字段类型会影响效率。字符串类型的精确匹配效率很高,但范围查询(比如日期范围)需要 B-tree 索引支持。图数据库大多默认用 B-tree。
复合索引
当查询条件里同时有多个字段时,复合索引能大幅减少扫描量。比如:
CREATE INDEX drug_target_index FOR (d:Drug) ON (d.target_id, d.confidence_score)
这个索引能同时加速 WHERE d.target_id = 'XXX' AND d.confidence_score > 0.8 这类查询。我在项目中遇到过一个问题:一开始只给 target_id 建了单属性索引,查询速度还行。但后来加了一个 confidence_score 的过滤条件,速度直接掉了一个数量级。为什么?因为数据库先通过 target_id 索引拿到一批节点,再挨个比对 confidence_score——这其实变成了二次过滤。换成复合索引后,一次索引查找就能定位到精确节点。
唯一索引
唯一索引不仅加速查询,还能保证数据唯一性。药物图谱里唯一ID字段(比如 ChEMBL ID、UniProt ID)特别适合用唯一索引:
CREATE CONSTRAINT unique_chembl_id FOR (d:Drug) REQUIRE d.chembl_id IS UNIQUE
我个人强推:所有能保证唯一的业务字段,都用唯一约束或唯一索引。这能避免很多数据合并时的脏数据问题。
全文索引
全文索引处理的是模糊匹配和文本搜索。药物图谱里大量数据是文本描述:药物说明书、文献摘要、基因功能注释。普通索引对 CONTAINS 或 STARTS WITH 无能为力,必须上全文索引。
Neo4j 的全文索引基于 Lucene,支持分词、词干提取、模糊搜索。创建示例:
CREATE FULLTEXT INDEX drug_description_index FOR (d:Drug) ON EACH [d.description, d.indications]
OPTIONS {indexConfig: {`fulltext.analyzer`: 'standard'}}
查询的时候用 CALL db.index.fulltext.queryNodes():
CALL db.index.fulltext.queryNodes('drug_description_index', 'anti-inflammatory AND (ibuprofen OR naproxen)')
YIELD node, score
RETURN node.name, score
ORDER BY score DESC LIMIT 20
全文索引有几个坑,我踩过:
- 分词器选择:药物名里有很多拉丁文、希腊文前缀(比如“cef-”、“-prazole”)。如果用默认的标准分词器,可能把“cef-triaxone”拆成三个 token,导致匹配失败。我后来换成了
keyword分词器,全字段当做一个 token 来处理。 - 索引更新频率:全文索引的构建和更新比普通索引慢得多。如果数据实时写入量很大,建议用异步方式重建索引,或者只在非高峰时段做完整重建。
- 评分机制:全文索引自带 TF‑IDF 评分,但药物图谱里不同字段的权重应该不同。比如
description字段的信息量远大于indications。你可以通过索引配置里的fulltext.eventually_consistent参数来权衡一致性和性能。
索引策略总览
下面这张图总结了三种索引的适用场景和核心参数。放在一起看会更清楚。
一些实操建议
索引不是越多越好。我曾经见过一个团队,给每个属性都建了索引,结果写入速度掉了 90%。记住几条原则:
- 只索引查询过滤器里的字段。如果某个字段只在返回结果里用,不参与
WHERE或JOIN条件,就别建索引。 - 定期检查索引命中率。Neo4j 的
EXPLAIN和PROFILE命令能告诉你查询是否走了索引。如果发现全表扫描(AllNodesScan),说明索引没被用上——赶紧查原因。 - 区分读重和写重。读重场景可以多建索引,甚至建覆盖索引;写重场景就得克制,优先保证写入吞吐。
- 索引也是一种数据。它需要存储空间、内存,以及写入时的维护时间。在设计索引策略时,要和业务方聊清楚:你愿意为查询速度牺牲多少写入性能?