精华 Neo4j官方教程:第21节,重构图数据模型
发布于 14 小时前 作者 pangguoming 14 次浏览 来自 分享

教程目录 引自:https://neo4j.com/docs/getting-started/data-modeling/tutorial-refactoring/

教程:重构图数据模型

重构是更改数据模型和图的过程。需要重构数据模型的主要原因包括:

  • 当前建模的图无法覆盖所有用例
  • 出现了新的用例
  • 用例的 Cypher® 查询在图扩展时性能不佳

为了满足这些需求,本教程将指导您设计、实现和测试一个经过重构的数据模型,并更新 Cypher 查询。

前置条件

本教程是《教程:创建图数据模型》的后续内容。在继续之前,您需要完成该教程中创建的数据模型。

或者,您也可以现在从头开始创建。选择您喜欢的部署方法,并使用以下代码添加数据:

CREATE (Apollo13:Movie {title: 'Apollo 13', tmdbID: 568, released: '1995-06-30', imdbRating: 7.6, genres: ['Drama', 'Adventure', 'IMAX']})
CREATE (TomH:Person {name: 'Tom Hanks', tmdbID: 31, born: '1956-07-09'})
CREATE (MegR:Person {name: 'Meg Ryan', tmdbID: 5344, born: '1961-11-19'})
CREATE (DannyD:Person {name: 'Danny DeVito', tmdbID: 518, born: '1944-11-17'})
CREATE (JackN:Person {name: 'Jack Nicholson', tmdbID: 514, born: '1937-04-22'})
CREATE (SleeplessInSeattle:Movie {title: 'Sleepless in Seattle', tmdbID: 858, released: '1993-06-25', imdbRating: 6.8, genres: ['Comedy', 'Drama', 'Romance']})
CREATE (Hoffa:Movie {title: 'Hoffa', tmdbID: 10410, released: '1992-12-25', imdbRating: 6.6, genres: ['Crime', 'Drama']})

MERGE (TomH)-[:ACTED_IN {roles:'Jim Lovell'}]->(Apollo13)
MERGE (TomH)-[:ACTED_IN {roles:'Sam Baldwin'}]->(SleeplessInSeattle)
MERGE (MegR)-[:ACTED_IN {roles:'Annie Reed'}]->(SleeplessInSeattle)
MERGE (DannyD)-[:DIRECTED]->(Hoffa)
MERGE (DannyD)-[:ACTED_IN {roles:'Robert "Bobby" Ciaro'}]->(Hoffa)
MERGE (JackN)-[:ACTED_IN {roles:'Hoffa'}]->(Hoffa)

CREATE (Sandy:User {name: 'Sandy Jones', userID: 1})
CREATE (Clinton:User {name: 'Clinton Spencer', userID: 2})

MERGE (Sandy)-[:RATED {rating:5}]->(Apollo13)
MERGE (Sandy)-[:RATED {rating:4}]->(SleeplessInSeattle)
MERGE (Clinton)-[:RATED {rating:3}]->(Apollo13)
MERGE (Clinton)-[:RATED {rating:3}]->(SleeplessInSeattle)
MERGE (Clinton)-[:RATED {rating:3}]->(Hoffa)

剩余或新用例

假设您想知道有哪些电影可以用某种语言观看。

要回答这个问题,您需要先将此信息添加到图中。然而,添加新数据会带来重复的风险,进而影响图的性能。

为演示这种情况,向 ‘Movie’ 节点添加新的 languages 属性及其对应值:

MATCH (Apollo13:Movie {title:'Apollo 13'})
MATCH (SleeplessInSeattle:Movie {title:'Sleepless in Seattle'})
MATCH (Hoffa:Movie {title:'Hoffa'})
SET Apollo13.languages = ['English']
SET SleeplessInSeattle.languages = ['English']
SET Hoffa.languages = ['English', 'Italian', 'Latin']

结果: image 要检索所有英语电影,执行此查询:

MATCH (m:Movie)
WHERE 'English' IN m.languages
RETURN m.title

结果将返回电影 “Apollo 13”、“Sleepless in Seattle” 和 “Hoffa”。

此查询检索所有 Movie 节点然后测试 languages 属性是否包含值 English。这种方法没错,但随着图的扩展可能遇到两个问题:

  1. 查询必须检索所有 Movie 节点 → 随着图的扩展,这种建模方式会降低查询性能
  2. language 属性值在多个 Movie 节点中重复 → 如果多个节点共享相同的属性值,说明该属性值可以作为新实体(如节点或关系)

解决方案是将 languages 属性重构为节点,并用新关系将其连接到 Movie 节点。 现在: image 重构: image

消除重复数据

要将节点属性 languages 重构为节点,可以使用以下查询:

MATCH (m:Movie)
WITH m, m.languages AS languages
UNWIND languages AS language
MERGE (l:Language {name: language})
MERGE (m)-[:IN_LANGUAGE]->(l)
REMOVE m.languages

查询分解说明:

  1. UNWIND Movie 节点的 languages 属性,将其条目转换为新的 Language 节点
  2. 创建 IN_LANGUAGE 关系连接 Movie 节点与其对应的 Language 节点
  3. 移除 Movie 节点的 languages 属性 如图: image 重构后,图中应只有一个值为 “English” 的 Language 节点,相关电影与其相连。这消除了大量重复并提高了图扩展时的性能。

处理复杂数据

假设出现了需要每部电影制片公司信息的新用例。制片公司数据包括物理地址,这可以视为复杂数据。

可以通过创建 ProductionCompany 节点和地址属性将此信息添加到图中:

CREATE (p:ProductionCompany {name:'Imagine Entertainment', country:'US', postalCode:90212, state:'CA', city:'Beverly Hills', address1:'10351 Santa Monica Blvd'})
MERGE (Apollo13:Movie {title:'Apollo 13'})
CREATE (p)-[:PRODUCED]->(Apollo13)
CREATE (jerseyFilms:ProductionCompany {name:'Jersey Films', country:'US', postalCode:90049, state:'CA', city:'Los Angeles', address1:'10351 Santa Monica Blvd'})
MERGE (hoffa:Movie {title:'Hoffa'})
CREATE (jerseyFilms)-[:PRODUCED]->(hoffa)

image 在节点上存储复杂数据的问题 以这种方式在节点上存储复杂数据可能存在以下问题:

  1. 数据重复 多个制片公司可能位于相同位置,导致信息在多个节点上重复 示例:之前我们将’languages’属性重构为节点,就是为了避免"English"在所有 Movie 节点上重复
  2. 过度获取 查询节点信息时需要不必要地检索更多同类节点 示例:要查找位于加利福尼亚的制片公司时,需要扫描所有 ProductionCompany 节点的属性来获取 state 值。而如果将加利福尼亚作为独立节点,就能提供更短的访问路径,避免获取多余信息 替代方案:可以创建索引

数据建模目标

数据建模的核心目标是减少查询时需要访问的图数据量。如果出现以下情况,需要考虑重构模型:

图中包含大量重复数据 查询仍在过度获取数据 当前模型分析 目前模型中新增了 ProductionCompany 节点标签,包含多个地址属性。这些属性值存在大量重复,不够理想。为提高效率,应:

检查重复的键值 考虑将其转换为新实体(节点或关系) 重构示例 由于两家制片公司都在加利福尼亚,可以:

将州属性转换为 State 节点 通过新的 LOCATED_AT 关系连接制片公司 移除制片公司节点上的 state 属性 重构效果 按州查询制片公司时可直接基于 State.name 值过滤 避免评估所有 ProductionCompany 节点的 state 属性 image 总结 处理复杂数据的重构方式取决于:

需要解答的问题 图扩展时查询的性能表现 下一步是通过测试来衡量图中的性能表现。

使用特定关系

当项目有需要经常检索某些信息的重复用例时,可以使用特定关系作为重构策略。好处包括:

  • 减少需要检索的节点数量
  • 提高查询性能

例如,如果经常需要检索 1995 年的演员信息,查询可以从:

MATCH (p:Person)-[:ACTED_IN]-(m:Movie)
WHERE p.name = 'Tom Hanks' AND m.released STARTS WITH '1995'
RETURN DISTINCT m.title AS Movie

优化为:

MATCH (p:Person)-[:ACTED_IN_1995]-(m:Movie)
WHERE p.name = 'Tom Hanks'
RETURN m.title AS Movie

重新测试图

重新测试图的用例查询

以下是需要重新检查的用例查询列表:

1. 哪些人参演了某部电影?

MATCH (p:Person)-[:ACTED_IN]->(m:Movie {title:'Hoffa'})
RETURN p

2. 谁导演了某部电影?

MATCH (p:Person)-[:DIRECTED]->(m:Movie {title:'Hoffa'})
RETURN p

3. 某人参演了哪些电影?

MATCH (p:Person {name:'Tom Hanks'})-[:ACTED_IN]->(m:Movie)
RETURN m

4. 有多少用户为某部电影评分?

MATCH (m:Movie {title: 'Apollo 13'})
RETURN COUNT {(:User)-[:RATED]->(m)} AS `Number of reviewers`

5. 谁是某部电影中最年轻的演员?

MATCH (p:Person)-[:ACTED_IN]-(m:Movie)
WHERE m.title = 'Hoffa'
RETURN p.name AS Actor, p.born as `Year Born` 
ORDER BY p.born DESC LIMIT 1

6. 某人在电影中扮演什么角色?

MATCH (p:Person {name:'Tom Hanks'})-[a:ACTED_IN]->(m:Movie {title: 'Apollo 13'})
RETURN a.roles

7. 某年按 IMDB 评分最高的电影是哪部?

MATCH (m:Movie)
WHERE m.released STARTS WITH '1995'
RETURN m.title as Movie, m.imdbRating as Rating 
ORDER BY m.imdbRating DESC LIMIT 1

8. 某演员参演了哪些剧情片?

MATCH (p:Person)-[:ACTED_IN]-(m:Movie)
WHERE p.name = 'Tom Hanks' AND
'Drama' IN m.genres
RETURN m.title AS Movie

9. 哪些用户给某部电影打了5分?

MATCH (u:User)-[r:RATED]-(m:Movie)
WHERE m.title = 'Apollo 13' AND
r.rating = 5
RETURN u.name as Reviewer

10. 哪些电影是英语电影?

MATCH (m:Movie)
WHERE m.languages = 'English'
RETURN m.title as Movie in English

说明

需要检查以上每个查询,看是否可以利用重构后的数据模型进行优化。比如最后一个查询"哪些电影是英语电影?“就可以使用新的 Language 节点和 IN_LANGUAGE 关系进行重写。 重构后,应重新检查所有用例查询。例如用例"哪些电影是英语的?”:

重构前:

MATCH (m:Movie)
WHERE m.languages = 'English'
RETURN m.title as Movie in English

重构后:

MATCH (m:Movie)-[:IN_LANGUAGE]->(l:Language)
WHERE l.name = 'English'
RETURN m.title as Movie in English

性能检查

在实际应用中,特别是图完全扩展时,可以通过 PROFILE 检查新查询的性能改进:

PROFILE MATCH (n:Person)
RETURN n

结果: image

持续学习

大多数重构工作是为了重新利用或添加更多信息到图中。

可以在 GraphAcademy 的 Graph Data Modeling Fundamentals 交互式课程中学习更多重构策略,如将 Person 节点拆分为 Actor 和 Director 节点、将 Movie 节点的 genre 属性转换为节点等。

回到顶部