mcpField
N
neo4j-modeling-skill

neo4j-modeling-skill

github.com/neo4j-contrib数据与数据库1181.2k 次安装1 周前更新
代码仓库

关于 neo4j-modeling-skill

neo4j-modeling-skill 为 Neo4j 图数据建模提供指导,适用于从零设计模型或审查、迁移现有模式。内容涵盖实体与关系选择、约束索引以及超节点处理。

主要功能

  1. 01从用例推导图模型
  2. 02区分节点、关系与属性
  3. 03迁移关系型模式到图
  4. 04检测并缓解超节点
  5. 05规划约束与索引

适用场景

  1. 01为新领域设计 Neo4j 图模式
  2. 02审查现有模式中的反模式
  3. 03将关系型或文档模式迁移到图
  4. 04处理高扇出节点及其性能风险

MCP 相关

README 提及 MCP 工具映射,可用 get-schema 检查模式,并通过 read-cypher 和 write-cypher 读取或创建约束。

安装

复制下面的命令,在终端中运行。

$npx skills add https://github.com/neo4j-contrib/neo4j-skills --skill neo4j-modeling-skill

安装后在 Claude Code 中输入/neo4j-modeling-skill确认已加载。

附带文件README.md

详细介绍

适用场景

  • 从头设计图模型(领域 → 节点、关系、属性)
  • 检查现有模型是否存在反模式
  • 决定使用节点、属性、关系还是标签
  • 将关系型或文档型架构迁移到图模型
  • 为多元或复杂关系设计中间节点
  • 检测并缓解超级节点/高扇出问题
  • 为模型选择并创建约束和索引

不适用场景

  • 编写或优化 Cypher → neo4j-cypher-skill
  • Spring Data Neo4j(@Node、@Relationship) → neo4j-spring-data-skill
  • GraphQL 类型定义 → neo4j-graphql-skill
  • 导入数据(LOAD CSV、APOC import) → neo4j-import-skill

设计前先检查

对于现有数据库,先运行以下命令——切勿在不了解当前状态的情况下提出更改:

CALL db.schema.visualization() YIELD nodes, relationships RETURN nodes, relationships;
SHOW CONSTRAINTS YIELD name, type, labelsOrTypes, properties RETURN name, type, labelsOrTypes, properties;
SHOW INDEXES YIELD name, type, labelsOrTypes, state WHERE state = 'ONLINE' RETURN name, type, labelsOrTypes;

如果有 APOC:

CALL apoc.meta.schema() YIELD value RETURN value;

MCP 工具对应关系:

操作工具
检查架构get-schema
SHOW CONSTRAINTS、SHOW INDEXESread-cypher
CREATE CONSTRAINT ... IF NOT EXISTSwrite-cypher(先展示并确认)

默认规则——适用于所有模型

  1. 先明确用例——设计前列出模型必须回答的 5 个以上查询
  2. 节点 = 具有标识的实体(名词);关系 = 具有方向的连接(动词)
  3. 标签使用 PascalCase;关系类型使用 SCREAMING_SNAKE_CASE;属性使用 camelCase
  4. 每种用于 MERGE 的节点类型都必须在其键属性上设置唯一性约束
  5. 在类型已知时添加属性类型约束(REQUIRE n.prop IS :: STRING)——及早发现错误写入。仅限 Enterprise;Community:使用唯一性约束,并在应用中验证类型
  6. 不使用通用标签(:Entity、:Node、:Thing);不使用通用关系类型(:RELATED_TO、:HAS)
  7. 安全标签(用于行级访问控制)应以通用前缀开头(例如 Sec),以便应用代码可靠地将其从领域架构中筛除
  8. 关系方向应编码语义含义,而非随意设定
  9. 对现有数据库提出任何更改前,先检查架构
  10. 所有约束/索引 DDL 都使用 IF NOT EXISTS——可安全地重复运行
  11. **在 Neo4j 2026.06+(Enterprise/Aura,正式发布)上:**使用一个代码块中的 ALTER CURRENT GRAPH TYPE SET { … } 声明完整模型,并使用 ALTER CURRENT GRAPH TYPE ADD { … } 扩展模型,而非逐条使用 CREATE CONSTRAINT 语句——详情请参阅 neo4j-cypher-skill(GRAPH TYPE 参考)。Community 不支持此功能(ALTER CURRENT GRAPH TYPE SET → “not supported in community edition”):请使用单独的约束。在 2026.02–2026.05 中,该语法为预览版。

关键模式

节点、关系与属性——决策表
问题答案建模为
它是一个具有标识、可作为查询入口的事物吗?是节点
它是两个事物之间具有方向的连接吗?是关系
该连接是否具有自身属性或多个目标?是中间节点
它是一个标量,总是与其父项一起返回、从不单独用于筛选吗?是父项上的属性
它是用于按类型筛选或路径遍历的类别吗?是标签(而非属性)
同一属性值是否在许多节点中重复出现(低基数)?是标签,而非属性节点
它是连接 2 个以上实体的事实吗?是中间节点
属性与标签——决策表
在以下情况下使用标签在以下情况下使用属性
值较少且固定,用作遍历筛选条件(WHERE n:Active)值很多、动态变化,或每个节点的值都唯一
按类型遍历(MATCH (n:VIPCustomer))按值筛选(WHERE n.tier = 'vip')
类别决定索引选择精细粒度的值用于范围扫描
示例::Active、:Verified、:Premium示例:status、score、email

规则:添加标签成本低;扫描所有 :Label 节点很快。切勿将高基数值建模为标签。

对于 status 这类低基数状态:如果它是稳定的类别,用于遍历/筛选,则使用标签(:Active);如果它经常变化或只随节点一起返回,则使用属性。切勿创建 (:Status) 节点。


中间节点模式

当关系需要自己的属性、连接超过 2 个实体,或需要独立查询时使用。

之前(带属性的关系——受限):

(Person)-[:ACTED_IN {role: "Neo"}]->(Movie)
// Cannot query roles independent of movies

之后(中间节点——可查询、可扩展):

(Person)-[:PLAYED]->(Role {name: "Neo"})-[:IN]->(Movie)
// MATCH (r:Role) WHERE r.name STARTS WITH 'Neo' RETURN r

雇佣关系重叠示例:

// Find colleagues who overlapped at same company
MATCH (p1:Person)-[:WORKED_AT]->(e1:Employment)-[:AT]->(c:Company)<-[:AT]-(e2:Employment)<-[:WORKED_AT]-(p2:Person)
WHERE elementId(p1) < elementId(p2)
  AND e1.startDate <= coalesce(e2.endDate, date())
  AND e2.startDate <= coalesce(e1.endDate, date())
RETURN DISTINCT p1.name, p2.name, c.name

在以下情况下,将关系提升为中间节点:

  • 关系有超过 2 个属性
  • 关系本身是另一个查询的主题
  • 多个实体共享相同的连接上下文
  • 需要在一个事实中连接超过 2 个实体

关系型 → 图迁移表
关系型构造图中的等价项备注
表行节点每个表使用一个标签(按需添加更多标签)
列(标量)节点属性
主键唯一性约束属性优先使用 tmdbId/personId,而非单独的 id
外键关系方向:从依赖项 → 被引用项
多对多联结表中间节点尤其适用于联结表有自身列的情况
联结表(无自身列)直接关系更简单;之后可升级为中间节点
NULL 外键(可选关系)不存在的关系不创建节点;缺失本身就是信号
多态外键(Rails 风格)多个标签或关系类型拆分为特定类型的关系
自引用外键同标签关系:Employee {managerId} → (e)-[:REPORTS_TO]->(m)
审计/历史列中间版本节点版本模式见参考资料

超级节点检测与缓解

检测:

// Find top-10 highest-degree nodes
MATCH (n)
RETURN labels(n) AS labels, elementId(n) AS id, count{ (n)--() } AS degree
ORDER BY degree DESC LIMIT 10

度数远高于其标签中位数的节点 = 超级节点候选。任何关系超过 100K 的节点,都会降低经过它的遍历查询性能。

原因:

  • 领域超级节点:机场、名人、热门话题标签——不可避免
  • 建模导致的超级节点:将性别、国家、状态建模为拥有数百万条边的节点——可以避免

缓解策略(按优先级排序):

策略使用场景实现方式
查询方向存在方向不对称性从低度数一侧查询;利用方向性
关系类型拆分超级节点承担多个角色使用 :FOLLOWS + :FAN,而非单一的 :RELATED_TO
标签隔离超级节点混合了多种实体类型使用 :Celebrity 与 :User 区分 → 仅查询相关子类型
分桶模式时间序列或高容量事件节点见下文
避免建模低基数分类值使用标签而非节点(:Active,而非 (:Status {name:"Active"}))
连接提示查询调优的最后手段在 Cypher 中使用 USING JOIN ON n

分桶模式(时间序列/高容量):

// Instead of: (:User)-[:VIEWED]->(:Page) (millions of rels per user)
// Bucket by hour:
(u:User)-[:VIEWED_IN]->(b:ViewBucket {userId: u.id, hour: '2025-04-28T14'})-[:VIEWED]->(p:Page)

// Query last hour's views without traversing full history:
MATCH (u:User {id: $uid})-[:VIEWED_IN]->(b:ViewBucket {hour: $hour})-[:VIEWED]->(p)
RETURN p.url

命名约定
元素约定正确示例错误示例
节点标签PascalCase,单数名词:Person、:BlogPost:person、:blog_posts、:Entity
关系类型SCREAMING_SNAKE_CASE,动词短语:ACTED_IN、:WORKS_FOR:actedin、:relatedTo、:HAS
属性键camelCasefirstName、createdAtFirstName、first_name
约束名称具有描述性的 snake_caseperson_id_uniqueconstraint1
索引名称具有描述性的 snake_caseperson_name_idxindex2

模式约束 — 各元素应创建的内容

所有 DDL 都使用 IF NOT EXISTS。在导入数据之前应用。

// 1. Uniqueness constraint — every node type used in MERGE
CREATE CONSTRAINT person_id_unique IF NOT EXISTS
  FOR (p:Person) REQUIRE p.id IS UNIQUE;

// 2. Existence constraint (Enterprise) — mandatory properties
CREATE CONSTRAINT person_name_exists IF NOT EXISTS
  FOR (p:Person) REQUIRE p.name IS NOT NULL;

// 3. Property type constraint (Enterprise) — enforce data type
CREATE CONSTRAINT person_born_integer IF NOT EXISTS
  FOR (p:Person) REQUIRE p.born IS :: INTEGER;

// 4. Key constraint (Enterprise) — unique + exists in one
CREATE CONSTRAINT movie_tmdbid_key IF NOT EXISTS
  FOR (m:Movie) REQUIRE m.tmdbId IS NODE KEY;

// 4b. Community alternative to NODE KEY — composite uniqueness (existence not enforced)
CREATE CONSTRAINT movie_title_released_unique IF NOT EXISTS
  FOR (m:Movie) REQUIRE (m.title, m.released) IS UNIQUE;

// 5. Range index — equality and range filters on properties
CREATE INDEX person_name_idx IF NOT EXISTS
  FOR (p:Person) ON (p.name);

// 6. Fulltext index — ranked free-text search via `db.index.fulltext.queryNodes` / `SEARCH` (Lucene syntax, wildcards like `An*`); not used for CONTAINS / STARTS WITH predicates
CREATE FULLTEXT INDEX person_fulltext IF NOT EXISTS
  FOR (n:Person) ON EACH [n.name, n.bio];

// 6b. Text index — serves CONTAINS / STARTS WITH / ENDS WITH (range index preferred for STARTS WITH when present)
CREATE TEXT INDEX person_bio_text IF NOT EXISTS
  FOR (n:Person) ON (n.bio);

// 7. Vector index — embedding similarity search
CREATE VECTOR INDEX chunk_embedding_idx IF NOT EXISTS
  FOR (c:Chunk) ON (c.embedding)
  OPTIONS { indexConfig: { `vector.dimensions`: 1536, `vector.similarity_function`: 'cosine' } };

// 8. Relationship index — filter on rel properties
CREATE INDEX acted_in_year_idx IF NOT EXISTS
  FOR ()-[r:ACTED_IN]-() ON (r.year);

创建索引后,轮询直到状态为 ONLINE:

SHOW INDEXES YIELD name, state WHERE state <> 'ONLINE' RETURN name, state;

状态为 ONLINE 之前,请勿使用索引。


向量/嵌入属性建模

对于长文本,优先使用专用的 :Chunk 节点;短文本的嵌入可以存放在实体上:

(:Document)-[:HAS_CHUNK]->(c:Chunk {text: "...", embedding: [...]})

规则:

  • Chunk 节点:text(源文本)、embedding(浮点数组)、chunkIndex(整数)
  • 父文档:仅存放元数据(title、url、createdAt)
  • 仅在 c.embedding 上创建向量索引
  • 生产环境默认使用 200–500 个 token 的分块大小,并设置 20% 重叠 [field]
  • 分块文档:不要将嵌入放在 :Document 上——这会使节点过大,并污染遍历

反模式表
反模式问题修正方法
通用标签 :Entity、:Node无过滤优势;会扫描所有节点使用领域标签 :Person、:Product
通用关系类型 :RELATED_TO、:HAS无法按关系类型筛选使用语义明确的类型 :PURCHASED、:AUTHORED
将低基数值建模为节点超级节点(:Status {name:"active"} → 数百万条边)改用标签 :Active
将属性用作标签(同时存在 n.type = 'VIP' 和 :VIP 标签)不一致、重复二选一;若用于遍历,优先使用标签
在业务节点上存储嵌入节点膨胀,遍历缓慢使用专用的 :Chunk 节点
使用 MERGE,但未设置唯一性约束悄悄创建重复节点在任何 MERGE 操作之前添加约束
未赋予关系方向明确含义方向随意;模型令人困惑方向应表示动作的语义流向
将连接表建模为裸属性丢失历史记录,难以扩展使用带有自身属性的中间节点
将 id 作为属性名称id(n) 是已弃用的 Cypher 函数(改用 elementId(n));在实际使用中,id 本身可以作为属性名称,但带领域限定的名称(personId、movieId)更清晰,也可避免未来产生歧义在有助于提升可读性的情况下,优先使用 personId、movieId、tmdbId
将所有日期存为字符串无法进行范围查询;无法使用时间运算符使用 Neo4j 的 date() 或 datetime() 类型

输出格式 — 模式评估

审查现有模型时:

## Schema Assessment

### Compliant
- [constraint / pattern that is correct]

### Issues Found
#### [Title] — Severity: ERROR / WARNING / INFO
- **Current**: what the model does
- **Problem**: why it is an issue
- **Fix**: specific Cypher DDL or model change

## Recommended Schema
### Node Labels
- :Label {key: TYPE, prop: TYPE, ...}  → constraints: [list]

### Relationships
- (:LabelA)-[:TYPE {prop: TYPE}]->(:LabelB)

### Constraints to Create
[CREATE CONSTRAINT ... statements]

### Indexes to Create
[CREATE INDEX ... statements]

严重性含义:

严重性含义操作
ERROR模型正确性故障(可能产生重复数据,存在数据丢失风险)停止;继续之前先修复
WARNING性能或可扩展性风险报告;继续之前询问用户
INFO风格或约定偏差告知用户;继续

来源标签

  • [official] — Neo4j 文档中直接说明
  • [derived] — 根据文档所述行为推导得出
  • [field] — 社区经验法则;将其视为默认值,但应进行验证

检查清单

  • 建模前定义用例(≥5 个查询)
  • 提出变更前检查现有数据库中的架构
  • 每个作为 MERGE 目标的节点标签都有唯一性约束
  • 不使用通用标签(:Entity、:Node、:Thing)
  • 不使用通用关系类型(:RELATED_TO、:HAS、:CONNECTED_TO)
  • 关系方向体现语义
  • 使用中间节点表示 N 元关系或带属性的关系
  • 将高基数值存储为属性,而非节点
  • 将低基数分类值用作标签,而非属性节点
  • 将嵌入存储在专用的 :Chunk 节点上,而非业务节点上
  • 识别并缓解超级节点候选项
  • 所有 DDL 均使用 IF NOT EXISTS
  • 使用索引前轮询,直至其状态为 ONLINE
  • 评估输出遵循上述结构化格式
  • 每项禁止事项都配有具体的修复方法

参考资料

按需加载:

评论

支持 Markdown