4. 药物知识图谱存储与查询

药物知识图谱一旦完成抽取与融合,就必须落到一个能承载“多跳关系、路径推理、模式灵活”的存储与查询系统上。关系型数据库擅长强一致事务与结构化统计,但在“药物—靶点—通路—疾病—不良反应”这类高度互联、深度不定的查询上,多表 JOIN 会迅速膨胀。图数据库把关系作为一等公民存储,天然适合药物知识图谱的遍历与推理。本节围绕选型、模式设计、索引优化与查询语言实践四条主线展开。

一、图数据库选型:先看查询形态,再看规模与运维

选型不应从“哪个最流行”出发,而应从查询形态出发。药物知识图谱的典型查询有三类:一是固定深度的邻域查询,如“某药物的直接靶点及其通路”;二是变长路径查询,如“从药物到不良反应之间不超过 4 跳的所有路径”;三是模式匹配与聚合,如“统计作用于同一靶点家族的药物数量”。前两类是图数据库的强项,第三类需要评估其聚合性能。

主流选择可分三档。属性图数据库(如 Neo4j、NebulaGraph、JanusGraph)以节点、关系、属性建模,查询语言成熟,适合中小规模到大规模的知识图谱,是药物图谱的首选。RDF 三元组库(如 Blazegraph、Virtuoso、Apache Jena)以 SPARQL 查询,适合需要与本体、语义网标准对齐的场景,尤其在药物本体(如 ChEBI、GO)集成时优势明显。图计算引擎(如 GraphX、Pregel 类)适合全图迭代算法,如 PageRank 排序药物重要性,但不适合交互式查询。

选型时需重点评估四个参数:数据规模(节点数、边数、属性数)、查询延迟要求(交互式通常要求亚秒级)、写入模式(批量导入还是持续增量)、运维成本(单机还是分布式)。例如,一个包含 50 万药物节点、200 万靶点节点、5000 万条关系的图谱,若以交互式多跳查询为主,Neo4j 单机或 NebulaGraph 集群均可胜任;若需与 SPARQL 端点对接,则应考虑 RDF 三元组库。

二、模式设计:把药物知识图谱的语义翻译成图结构

模式设计决定图谱能否高效查询。药物知识图谱的核心实体包括药物、靶点、基因、通路、疾病、不良反应、代谢酶、转运体等;核心关系包括“药物—作用于—靶点”“靶点—参与—通路”“药物—治疗—疾病”“药物—引起—不良反应”“药物—代谢—酶”等。设计时需遵循三条原则。

第一,关系方向要符合语义。例如“药物—作用于—靶点”与“靶点—被作用于—药物”是同一事实的两种方向,应统一为一种,避免双向冗余。第二,属性应尽量内联到节点或关系上,而非拆成额外节点。例如药物的“批准年份”“半衰期”应作为药物节点属性,而不是创建“年份”节点再连边。第三,对高频查询路径建立“快捷关系”。例如“药物—治疗—疾病”若需频繁查询,可在药物与疾病之间直接建边,同时保留“药物—作用于—靶点—参与—通路—关联—疾病”的完整路径,前者用于快速检索,后者用于推理溯源。

一个常见的模式设计示例如下:药物节点标签为 Drug,属性包括 drug_id、name、smiles、approval_year;靶点节点标签为 Target,属性包括 target_id、gene_symbol、uniprot_id;关系类型为 ACTS_ON,属性包括 affinity、source。这种设计使“查询某药物所有靶点”只需一次关系遍历。

药物知识图谱模式与查询路径示意 Drug drug_id / name Target gene_symbol Pathway pathway_id Disease disease_id AdverseEvent event_id Enzyme enzyme_id ACTS_ON PARTICIPATES_IN ASSOCIATED_WITH CAUSES TREATS METABOLIZED_BY 快捷关系(可选) 图例: 药物—靶点 靶点—通路 通路—疾病 药物—不良反应 快捷关系 说明:实线为完整语义路径,虚线为高频查询的快捷关系,二者可共存。

三、索引优化:让多跳查询从秒级降到毫秒级

图数据库的索引与关系型数据库不同,主要分为节点属性索引、关系属性索引和全文索引。药物知识图谱中,最常用的索引是节点属性索引,例如在 Drug.name、Target.gene_symbol、Disease.disease_id 上建立索引,使“按名称查药物”或“按基因符号查靶点”能直接定位节点,避免全图扫描。

索引优化的关键在于识别查询的“入口点”。例如查询“阿司匹林作用于哪些靶点”,入口是药物名称,因此 Drug.name 必须有索引;若查询“哪些药物作用于 COX-1”,入口是靶点基因符号,因此 Target.gene_symbol 必须有索引。对于变长路径查询,如“从药物到不良反应不超过 4 跳”,入口仍是药物节点,索引同样关键。

此外,需注意索引的代价:每个索引都会增加写入开销和存储占用。因此不应盲目为所有属性建索引,而应基于查询日志统计高频入口属性。一个实用的做法是:先上线无索引版本,收集慢查询日志,再针对慢查询的入口属性建索引,迭代优化。对于超大规模图谱,还可考虑复合索引,例如在 Drug 节点上对 (name, approval_year) 建复合索引,支持“某年批准且名称包含某关键词”的查询。

四、查询语言实践:从 Cypher 到 SPARQL

属性图数据库常用 Cypher(Neo4j)、nGQL(NebulaGraph)或 Gremlin。以 Cypher 为例,查询“阿司匹林作用于哪些靶点及其通路”可写为:

MATCH (d:Drug {name: 'Aspirin'})-[:ACTS_ON]->(t:Target)-[:PARTICIPATES_IN]->(p:Pathway)
RETURN d.name AS drug, t.gene_symbol AS target, p.pathway_id AS pathway
ORDER BY target;

该查询从 Drug.name 索引定位阿司匹林节点,沿 ACTS_ON 关系找到靶点,再沿 PARTICIPATES_IN 找到通路。若 Drug.name 和 Target.gene_symbol 均有索引,查询可在毫秒级返回。

对于变长路径查询,Cypher 支持 *1..4 语法:

MATCH path = (d:Drug {name: 'Aspirin'})-[:ACTS_ON|PARTICIPATES_IN|ASSOCIATED_WITH*1..4]->(a:AdverseEvent)
RETURN path, length(path) AS hops
ORDER BY hops
LIMIT 20;

该查询返回从阿司匹林到不良反应的所有路径,并按跳数排序。实际使用时需限制跳数和返回条数,避免路径爆炸。若使用 RDF 三元组库,等价查询用 SPARQL 表达,需先定义前缀,再以 ?drug :name "Aspirin" 为入口,沿属性路径 :actsOn/:participatesIn 遍历。SPARQL 的优势在于可直接复用药物本体中的类与属性定义,适合与 ChEBI、GO 等标准本体对齐的场景。

查询语言实践建议:先用 EXPLAIN 或 PROFILE 查看执行计划,确认是否命中索引;若出现 AllNodesScan,说明入口属性缺少索引,应优先补充。

五、一个可复用的查询优化检查清单

在实际工程中,可按以下清单逐项检查:入口属性是否已建索引;查询是否限制了返回条数和跳数;是否使用了 PROFILE 验证执行计划;是否存在全图扫描;变长路径是否设置了上限;高频查询是否已建立快捷关系;写入批量是否合理(批量导入通常比逐条写入快一个数量级)。该清单可直接用于药物知识图谱查询性能的日常巡检。

综上,药物知识图谱的存储与查询是一个“选型—建模—索引—查询”联动的工程问题。选型决定能力边界,模式设计决定语义表达,索引优化决定响应速度,查询语言实践决定落地效率。四者协同,才能让图谱从“存得下”走向“查得快、推得准”。