从关系型数据库迁移到图数据库

介绍

本文旨在解释关系型数据库和图数据库结构以及数据模型之间的概念差异。它还概述了如何以类似或不同的方式使用每种数据库类型,从关系型和图查询语言到与应用程序交互。

关系型数据库概述

关系型数据库自 80 年代以来一直是软件应用程序的“主力”,并且至今仍是如此。它们将高度结构化的数据存储在具有预定义的特定类型列和许多包含这些定义类型信息的行的表中。由于其组织的严格性,关系型数据库要求开发人员和应用程序严格地构建应用程序中使用的数据。

在关系型数据库中,对其他行和表的引用是通过外键列引用主键属性来指示的。联接在查询时通过匹配所有连接表中所有行的主键和外键来计算。这些操作计算量大,内存占用量大,成本呈指数级增长。

当模型中出现多对多关系时,您必须引入一个联接表(或关联实体表),该表包含两个参与表的两个外键,从而进一步提高联接操作成本。下图显示了此概念,通过创建包含一个列中的人员 ID 和下一列中相关部门 ID 的人员-部门联接表,将人员(来自人员表)连接到部门(在部门表中)。

正如您可能看到的那样,这使得理解连接变得非常麻烦,因为您必须知道人员 ID 和部门 ID 值(执行其他查找以找到它们)才能知道哪个人与哪些部门连接。这些类型的昂贵联接操作通常通过对数据进行反规范化来解决,以减少必要的联接次数,从而破坏关系型数据库的数据完整性。

尽管并非所有用例都适合这种严格的数据模型,但缺乏可行的替代方案以及对关系型数据库的广泛支持使得替代模型难以进入主流。然而,NoSQL 时代出现在市场上,满足了用户和企业的某些需求,但仍然忽略了数据之间连接的重要性。这就是图数据库的诞生方式。它们旨在为我们今天所处的互联世界提供最大的优势。

将关系型知识转换为图

与其他数据库管理系统不同,在图数据模型中,关系与数据本身一样重要。这意味着我们不需要使用外键等特殊属性或类似于 map-reduce 的带外处理来推断实体之间的连接。

通过将节点和关系组装成连接的结构,图数据库使我们能够构建简单而复杂的模型,这些模型与我们的问题域密切相关。数据与其在现实世界中的形式惊人地相似——小巧、规范化,但实体之间关系丰富。这使您能够从任何可以想象的兴趣点查询和查看您的数据,支持许多不同的用例。

图数据库模型中的每个节点(实体或属性)都直接且物理地包含一个关系记录列表,这些记录代表与其他节点的关系。这些关系记录按类型和方向进行组织,并且可能包含其他属性。每当您运行等效于联接操作时,图数据库都会使用此列表,直接访问连接的节点,并消除对昂贵的搜索匹配计算的需求。

这种将关系预先物化到数据库结构中的能力使 Neo4j 能够提供比其他数据库高几个数量级的性能,尤其是对于联接密集型查询,允许用户利用从分钟到毫秒的优势。

数据模型差异

正如您可能从上面讨论的结构差异中想象的那样,关系型与图的数据模型非常不同。简单的图结构导致比使用传统关系型或其他 NoSQL 数据库生成的模型更简单、更具表现力的数据模型。

如果您习惯使用关系型数据库进行建模,请记住精心设计、规范化的实体关系图的简单性和美观性——一个简单、易于理解的模型,您可以与同事和领域专家快速在白板上进行绘制。图正是这样——领域模型的清晰表达,专注于您想要有效支持的用例。

让我们比较两种数据模型,以展示关系型和图之间结构的差异。

在上面的关系型示例中,我们在左侧的 Person 表(可能数百万行)中搜索以找到用户 Alice 及其人员 ID 815。然后,我们在人员-部门表(橙色中间表)中搜索以找到所有引用 Alice 人员 ID(815)的行。检索到 3 个相关行后,我们转到右侧的部门表以搜索部门 ID 的实际值(111、119、181)。现在我们知道 Alice 是 4Future、P0815 和 A42 部门的成员。

在上面的图版本中,我们有一个带有“人员”标签的 Alice 节点。Alice 属于 3 个不同的部门,因此我们为每个部门创建一个节点,并标记为“部门”。要找出 Alice 属于哪些部门,我们会搜索图中 Alice 的节点,然后遍历从 Alice 到所有部门节点的“属于”关系。这就是我们所需要的——单跳,没有查找。

有关此主题的更多信息,请参阅数据建模部分

数据存储和检索

使用 SQL 查询关系型数据库非常容易。SQL 是一种声明式查询语言,它允许在数据库工具中轻松进行即席查询,也可以从应用程序代码中执行特定用例的查询。即使是对象关系映射器(ORM)也在后台使用 SQL 与数据库进行通信。

图数据库是否有类似的东西?当然有!

Cypher® 是 Neo4j 的声明式图查询语言,它建立在 SQL 的基本概念和子句之上,但增加了许多特定于图的功能,使您能够轻松地处理图模型。

如果您曾经尝试编写包含大量联接的 SQL 语句,您就会知道,由于 SQL 语法中的大量技术噪音,您很快就会失去对查询实际功能的了解。在 Cypher 中,语法保持简洁,专注于域组件及其之间的连接,以更直观、更清晰的方式表达查找或创建数据的模式。除了基本模式匹配之外的其他子句看起来与 SQL 非常相似,因为 Cypher 是建立在先驱语言的基础之上的。

我们将在即将发布的指南中介绍 Cypher 查询语言语法,但让我们简要看一下 SQL 查询与 Cypher 查询有何不同。在上面数据建模示例中的组织领域,SQL 语句 **列出 IT 部门的员工** 会是什么样子,它与 Cypher 语句有何比较?

SQL 语句
SELECT name FROM Person
LEFT JOIN Person_Department
  ON Person.Id = Person_Department.PersonId
LEFT JOIN Department
  ON Department.Id = Person_Department.DepartmentId
WHERE Department.name = "IT Department"
Cypher 语句
MATCH (p:Person)-[:WORKS_AT]->(d:Dept)
WHERE d.name = "IT Department"
RETURN p.name

您可以在即将发布的关于 Cypher 查询语言 的章节中找到更多关于 Cypher 语法的信息,以及关于如何从 SQL 迁移到 Cypher 的信息。

从关系型数据库到图数据库的迁移 - 实践

如果您决定将数据从关系型数据库迁移到图数据库,将应用程序迁移到使用 Neo4j 的步骤实际上非常简单。您可以使用专为您的堆栈或编程语言设计的驱动程序或连接器库连接到 Neo4j,就像您连接到其他数据库一样。由于 Neo4j 及其社区的存在,几乎所有流行的编程语言都有 Neo4j 驱动程序,它们模仿现有的数据库驱动程序习惯用法和方法。

例如,Neo4j JDBC 驱动程序可以像这样用于查询数据库以查找 *John 的部门*

Connection con = DriverManager.getConnection("jdbc:neo4j://127.0.0.1:7474/");

String query =
    "MATCH (:Person {name:{1}})-[:EMPLOYEE]-(d:Department) RETURN d.name as dept";
try (PreparedStatement stmt = con.prepareStatement(QUERY)) {
    stmt.setString(1,"John");
    ResultSet rs = stmt.executeQuery();
    while(rs.next()) {
        String department = rs.getString("dept");
        ....
    }
}

有关更多信息,您可以访问我们的 构建应用程序 页面,了解如何使用不同的编程语言连接到 Neo4j。