Neo4j:图数据库入门:节点、关系、标

一句话价值:图数据库的概念其实比关系型数据库还少,核心就三样东西。但 Neo4j 里有两个"看起来多余"的设置------公共标签 和别名------恰恰是一张图能不能干活的开关。这篇用一张真实的奶茶图谱把它们讲透。


从一个"查不到"说起

先看一个真实的翻车。我建了一张奶茶知识图谱,里面有这样一条事实:

scss 复制代码
(台湾奶茶:Type)-[:属于]-(珍珠奶茶:Product)

然后用户问:

台式奶茶的饮品都有哪些配料?

Cypher 是这么写的:

cypher 复制代码
MATCH (t:Type {name: '台式奶茶'})<-[:属于]-(p:Product)-[:包含]->(i:Ingredient)
RETURN DISTINCT p.name, i.name

结果返回 空数组。

不是语法错了,也不是数据缺失。而是------库里根本没有一个节点叫"台式奶茶",它叫"台湾奶茶"。

属性匹配是精确的,差一个字,就是查不到。这个坑后面几篇会反复用到,先记住它。

要搞懂为什么会这样、以及怎么救,得先回到最底层:图数据库到底是怎么存东西的。


一、图数据库只有三样东西

比你想象的简单:

概念 是什么 奶茶图谱里的例子
节点 Node 一个实体 珍珠奶茶、珍珠、煮制、年轻人
关系 Relationship 两个节点之间的连线,有方向、有类型 (珍珠奶茶)-[:包含]->(珍珠)
属性 Property 挂在节点或关系上的键值对 name、description、aliases

用 Cypher 写出来就是一个节点长这样:

cypher 复制代码
(台湾奶茶:Type:Entity {
    name:        "台湾奶茶",
    aliases:     ["台式奶茶", "台茶"],
    description: "台湾奶茶,源自台湾的奶茶流派,以珍珠、芋圆等丰富配料和偏甜的口味著称。",
    embedding:   [0.12, -0.45, ... 共 1024 个数]
})

拆开看:

  • 台湾奶茶 是变量名,只在当前这条语句里有效,不是存储的一部分
  • :Type:Entity 是标签(下面细讲)
  • {...} 里的全是属性

注意最后两行:aliases 和 embedding 不是什么特殊机制 ,它们就是跟 name 一样的普通属性。只不过一个的值是列表,一个是一千多个浮点数的列表。图数据库不关心里面装的是什么。


二、关系:图数据库的立身之本

关系型数据库里,"珍珠奶茶包含珍珠"这个事实表现为两张表 + 一个外键 ,两边的关联是查询时用 JOIN 临时算出来的。

图数据库里,这条关系是一个物理存在的对象:

cypher 复制代码
MATCH (p:Product {name: "珍珠奶茶"}), (i:Ingredient {name: "珍珠"})
CREATE (p)-[:包含]->(i)

[:包含] 这个方括号里的东西,就是关系------它有类型(包含)、有方向(Product → Ingredient),还能有自己的属性 (比如 {since: "2024"})。

更关键的是它在存储层的实现:每个节点直接存着"我的第一条关系在哪",每条关系存着"我的前后关系在哪"。所以"从 A 走到它的邻居"是一次指针跳转 ,跟全库有多少节点无关。这就是所谓的免索引邻接。

这个原理我单独写过一篇(《图数据库为什么查关系快:免索引邻接》),这里不展开。记住一句话就够:关系是"存出来的",不是"查出来的"。


三、标签:回答"这是什么"

标签(Label)回答一个问题:这个节点属于哪一类?

类比一下:

  • MySQL 靠表名 区分:users 表里的行是人,orders 表里的行是订单
  • 图数据库靠标签 区分:带 :Product 标签的节点是奶茶产品,带 :Ingredient 的是配料

奶茶图谱里有 6 个标签:

python 复制代码
Product  Type  Ingredient  Method  People  Entity

前 5 个是业务标签,表达"这是什么"------这就是我们给 LLM 写 prompt 时描述的那套 schema:

diff 复制代码
节点:
- Product: 奶茶品牌
- Ingredient: 配料
- Type: 奶茶类型
- Method: 制作工艺
- People: 适合人群

关键点来了:一个节点可以同时有多个标签。 这正是让很多人懵的地方。


四、:Entity 是什么------一个"看起来多余"的公共标签

把库里的真实数据捞出来看:

节点 它的标签
珍珠奶茶 ["Product", "Entity"]
台湾奶茶 ["Type", "Entity"]
珍珠 ["Ingredient", "Entity"]
煮制 ["Method", "Entity"]
年轻人 ["People", "Entity"]

每个节点都多带了一个 Entity。 这个标签不带任何业务含义,它只说一件事:"我是个实体。"

为什么非加不可?因为 Neo4j 建索引必须指定标签:

cypher 复制代码
CREATE VECTOR INDEX entity_desc FOR (n:Entity) ON (n.embedding)
                                   ^^^^^^^^^ 只能针对某一个标签

如果不加这个公共标签,你就得给 5 个业务标签各建一遍索引:

diff 复制代码
5 个向量索引(Product 一个、Type 一个、Ingredient 一个......)
+ 5 个全文索引

查询时还要写 5 个 UNION 把它们拼起来。

加了 Entity 之后,建一次就覆盖全部:

cypher 复制代码
CALL db.index.vector.queryNodes('entity_desc', 6, $vec)
YIELD node, score
RETURN node.name, labels(node)   // 捞出来的可能是 Product,也可能是 Ingredient

这一行查询不需要知道用户问的是产品还是配料 ------统一在 Entity 这层捞就完了。

而这恰恰是混合检索能成立的前提:用户问"喝了会上瘾的那种奶茶"时,系统事先并不知道该去哪个业务标签里找。公共标签让"一把捞住所有实体"成为可能。

一句话理解:业务标签是给人和 LLM 看的(这是什么),公共标签是给索引和检索引擎用的(能被一把捞住)。


五、aliases 是什么------别名表

回到开头那个翻车。"台式奶茶"查不到,因为库里叫"台湾奶茶"。

最直觉的解法是让模型去理解"台式≈台湾"。但这条路不可靠------实测用向量检索,"台式奶茶"这个说法只把 台湾奶茶 排到第 7 位,压根进不了候选。

唯一确定的方式是显式登记:你白纸黑字地告诉系统,这两个词指同一个东西。

这就有了别名表:

规范名(name) 别名(aliases)
台湾奶茶 ["台式奶茶", "台茶"]
珍珠奶茶 ["波霸奶茶", "珍奶"]
珍珠 ["波霸", "粉圆"]
奶盖 ["芝士奶盖", "奶泡"]
学生 ["学生党"]

它就是一个普通的列表属性------一个东西可以有零个或多个别名。

有了它之后,建索引时把别名一起索引进去:

cypher 复制代码
CREATE FULLTEXT INDEX entity_fulltext
FOR (n:Entity) ON EACH [n.name, n.aliases, n.description]

于是用户说"台式奶茶",倒排索引里有"台式"这个词 (因为它来自别名表),就能命中 台湾奶茶 这个节点。

对照一下:

加上别名之前 加上别名之后
MATCH (t:Type {name:'台式奶茶'}) → [] 全文索引命中 → 拿到 台湾奶茶 节点
三种检索全挂 混合检索答对

六、这三个概念,别混在一个层面

初学者最容易懵的地方,是把它们当成同一类东西。其实它们分工很明确:

arduino 复制代码
name     规范名    ------ 图谱的"官方名字",唯一。业务概念
aliases  别名      ------ 用户可能说的其它叫法,可多个,人工维护。业务概念
Entity   公共标签  ------ 让所有实体能被同一个索引一把覆盖。纯技术手段

前两个回答"怎么让人找得到你 ",第三个回答"怎么让索引只建一次"。

想清楚这一层,再看 Neo4j 里任何一张图,都不会懵了。


本篇小结

  1. 图数据库核心只有三样:节点、关系、属性。aliases 和 embedding 都只是普通属性,没有特殊机制。
  2. 关系是物理存储的对象(免索引邻接),不是查询时 JOIN 出来的------这是图数据库快在多跳的根本原因。
  3. 标签回答"这是什么";一个节点可以有多个标签。
  4. 公共标签 :Entity 是纯技术手段,让索引只需建一次、检索能一把捞住任意类型的实体。
  5. 别名 aliases 是人工维护的业务资产,解决"用户说的词和库里存的词对不上"的问题------台式奶茶 能查到 台湾奶茶,全靠它。
  6. 前两个是业务概念,第三个是技术手段,不在一个层面,别混着理解。

系列预告

下一篇往上一走:为什么有了 embedding 还要配关键词检索、还要配图? 答案藏在三类不同的"敏感"里------字面敏感、词义敏感、词汇表敏感,它们各自要一种检索来治。开头的"台式奶茶"会在那里被彻底拆开。

相关推荐
李溪白1 小时前
RAG 进阶:Agentic RAG 与多智能体协作(系列二·第1篇):高级工具设计
agent
杨超越luckly1 小时前
国庆V型、春节迁徙、五一升温:节假日数据里的中国旅游
agent·旅游·数据可视化·节假日·cpi·国定假日
熊猫钓鱼>_>1 小时前
当工具成为类:Softmax困境与连接器过载的算法根源
算法·ai·agent·工具·曲线·华为云码道·生态图鉴
云半S一1 小时前
Agent 学习总结一
经验分享·笔记·学习·agent
桃桃乌龙_2 小时前
从 ChatGPT 到 Agent Harness:近五年 AI 技术演进全景(MCP / Skills / A2A 详解)
人工智能·agent
Topskys2 小时前
RAG 准确率提升体系化方案
agent
ADark2 小时前
FDE 入门 · 11|数据那一摊:项目卡死的地方通常在这儿
人工智能·agent
武子康2 小时前
AI Agent 设备指令超时后,重试会执行两次吗?
人工智能·后端·agent
程序猿DD2 小时前
有了这个无限画布,我也能做漫画了!
agent