# 3. 数据模型与查询语言


用于数据存储和查询的通用数据模型

![big data](/images/db/db_start.jpg)

<!-- more -->


## 1. 数据的抽象
大多数应用程序是通过一层一层叠加数据模型来构建的，每一层都面临的关键问题是: 如何将其用下一层来表示？例如: 
1. 应用程序开发人员通过对象或数据结构，以及操作这些数据结构的API对数据进行建模
2. 存储时，需要通过特定的**数据模型**(JSON/XML/图/关联的行)对这些数据进行序列化
3. 数据库工程师决定使用什么硬件以及**字节格式**来存储序列化之后的数据

从应用程序到最后的磁盘存储可能会有很多的中间层，但基本的思想是相同的: 每层都通过提供一个简洁的数据模型来隐藏下层的复杂性。采用什么样的数据模型决定了层与层之间转换的效率和复杂度，以及数据支持的查询方式。

目前大多数应用程序开发都使用面向对象的编程语言来开发，程序运行时，数据是程序中的对象，保存在程序的数据结构中，数据之间通过引用，可以进行任意关联。当数据被存储时，程序中的对象被转换成数据模型，数据结构之间的任意引用反映在模型构造的数据关系中。因此，对于数据而言，应用的需求就是如何更加便利的表示程序中的对象，并反应应用内**数据之间的关系**(一对一，一对多，多对一还是多对多)。

接下来我们将比较关系模型、文档模型和一些基于图的数据模型。

## 2. 数据模型
### 2.1 关系模型
关系模型中，数据被组织成关系（SQL中称作表），其中每个关系是元组（SQL中称作行)的无序集合。在这种模型中:
1. 表具有固定的模式，并通过外键和连接，来表示数据之间的关系

将应用程序中的**对象以及他们的之间的引用关系**，保存在关系型数据库中的表，行，列时需要一个显示的中间转换层，虽然对象关系映射（object-relational mapping, ORM） 框架可以减少这个转换层所需的样板代码的数量，但是它们不能完全隐藏这两个模型之间的差异。模型之间的脱离有时被称为阻抗失谐。

但是除此之外关系模型在数据的规范化、查询的便捷性、联合的支持上都表现优秀。关系数据库的查询优化器称得上是一个复杂的怪兽，但是其核心是: 只需构建一次然后所有使用该数据库的应用程序都可以从中受益。

### 2.2 文档模型
文档有多种形式，比如 xml，json，最常见属 json。 json 通过在其父记录中存储嵌套记录，而不是在单独的表中，可以很自然的表示一对多关系。**嵌套存储意味着数据存在树状结构**，JSON 表示将该树结构现实化。这种自包含的文档比关系型中的多表模式具有更好的局部性。

这种**局部性**的性能优势，只有在应用程序经常需要访问整个文档时才会显现。只访问其中的一小部分，这对于大型文档来说是很浪费的。**更新文档时，通常需要整个重写。只有不改变文档大小的修改才可以容易地原地执行**。因此，通常建议保持相对小的文档，并避免增加文档大小的写入。这些性能限制大大减少了文档数据库的实用场景。

文档模型的另一个显著缺点是在文档数据库中，对联合的支持通常很弱。不支持联合导致在表示多对一和多对多时产生内容的重复问题。显然文档模型也很难表示多对多关系。

比如用户所在地区的表示，无论是存储ID还是文本字符串，都涉及内容重复的问题。当使用ID时，对人类有意义的信息只存储在一个地方，引用它的所有内容都使用 ID。当直接存储文本时，则使用它的每条记录都保存了一份这样可读信息。使用 ID 的好处是，因为它对人类没有任何直接意义，所以永远不需要直接改变：及时 ID 标识的信息发生了变化，它也可以保持不变。相反以文本存储的信息将产生冗余副本，在这些信息发生变化时这些冗余副本都需要更新，并且存在数据不一致的风险。消除这种重复正是**数据库规范化**的核心思想(**模式和反模式**)。

### 2.3 网络模型
在网络模型中，记录之间的链接不是外键，而更像是编程语言的指针。访问记录的唯一方法是选择一条始于根记录的路径，并沿着相关链接依次访问。这种命令式的查询方式(你必须告诉数据库如何去查询，而不是告诉它你需要什么)使得查询和更新数据变得异常复杂而没有灵活性。所以网络模型基本已不再使用。

### 2.4 数据模型的灵活性
文档数据库有时称为无模式（schemaless） 一个更精确的术语是读时模式（schema-on-read） （数据的结构是隐含的，只有在数据被读取时才被解释） ，相应的是写时模式（schema-on-write） （传统的关系数据库方法中，模式明确，且数据库确保所有的数据都符合其模式）

当由于某种原因（例如，数据是异构的） 集合中的项目并不都具有相同的结构时,读时模式更具优势。例如，
- 如果：存在许多不同类型的对象，将每种类型的对象放在自己的表中是不现实的。
- 数据的结构由外部系统决定。你无法控制外部系统且它随时可能变化。

在上述情况下，模式的坏处远大于它的帮助，无模式文档可能是一个更加自然的数据模型。但是，要是所有记录都具有相同的结构，那么模式是记录并强制这种结构的有效机制。

### 2.5 模型比较
总结起来文档数据模型具有如下优点:
1. 架构灵活性，有一定的局限性，因局部性而拥有更好的性能
2. 对于某些应用程序而言更接近于应用程序使用的数据结构
3. 文档数据库对连接的糟糕支持也许或也许不是一个问题，这取决于应用程序

因此如果应用程序中的数据具有类似文档的结构（即，一对多关系树，通常一次性加载整个树），那么使用文档模型可能是一个好主意。如果你的应用程序确实使用多对多关系，那么文档模型就没有那么吸引人了。通过反规范化可以减少对连接的需求，但是应用程序代码需要做额外的工作来保持数据的一致性。通过向数据库发出多个请求，可以在应用程序代码中模拟连接，但是这也将复杂性转移到应用程序中，并且通常比由数据库内的专用代码执行的连接慢。在这种情况下，使用文档模型会导致更复杂的应用程序代码和更差的性能

关系模型则具有如下优点:
1. 为连接提供更好的支持
2. 支持多对一和多对多的关系

**对于高度相联的数据，选用文档模型是糟糕的，选用关系模型是可接受的，而选用图形模型（参见“图数据模型”） 是最自然的**

### 2.6 模型的融合
随着时间的推移，关系模型和文档模型已经开始相互借鉴。SQL 标准增加了对结构化数据类型， XML 和 json 数据的支持；这允许将多值存储在单行内，并支持在这些文档内的查询和索引。

文档数据也越来越多的支持连接。RethinkDB在其查询语言中支持类似关系的连接，一些MongoDB驱动程序可以自动解析数据库引用。在表示多对一和多对多的关系时，关系数据库和文档数据库并没有根本的不同。在这两种情况下，相关项目都被一个唯一的标识符引用，这个标识符在关系模型中被称为外键，在文档模型中称为文档引用。该标识符在读取时通过连接或后续查询来解析。

随着时间的推移，关系数据库和文档数据库似乎变得越来越相似，这是一件好事：数据模型相互补充 ，如果一个数据库能够处理类似文档的数据，并能够对其执行关系查询，那么应用程序就可以使用最符合其需求的功能组合。关系模型和文档模型的混合是未来数据库一条很好的路线。

## 3. 查询语言
我们将数据存入数据库的目的是为了在需要的时候将其读取出来再次使用。数据库的查询有两种明显不同的方式:
1. 声明式语言: 
    - 指定所需数据的模式，即结果必须符合哪些条件，以及如何将数据转换（ 例如，排序，分组和集合） 但不是如何实现这一目标
    - 数据库系统的查询优化器决定使用哪些索引和哪些连接方法，以及以何种顺序执行查询的各个部分
2. 命令式代码: 告诉计算机以特定顺序执行某些操作

声明式查询语言通常比命令式API更加简洁和容易。但更重要的是，它还隐藏了数据库引擎的实现细节，这使得数据库系统可以在无需对查询做任何更改的情况下进行性能提升。

最后，声明式语言通常适合与并行执行，而命令式代码由于指定了特定的执行顺序，很难在多核和多台机器上并行化。声明式语言则对于并行执行更为友好，它们仅指定了结果所满足的模式，而不是如何得到结果的具体算法。

## 4. 图数据模型
多对多关系是不同数据模型之间的重要区别特征:
1. 如果数据大多是一对多关系(树结构数据)或者记录之间没有关系，那么文档模型最合适
2. 关系模型能够处理简单的多对多关系
3. 随着数据之间的连接变得更加复杂，将数据建模为图形显得更加自然。

图真正强大的地方在于，提供了单个数据存储区中保存完全不同类型对象的一致方式。图中的顶点可以表示任意类型，边可以表示任意对象之间的任意关系。

图模型与文档模型有一个共同点，它们通常不会对存储的数据强加某个模式，这可以使应用程序更加适应不断变化的需求。但是应用程序仍然会假定数据具有一定的结构，只不过是模式是显示(**写时强制**)还是隐式(**读时处理**)的问题。

图分为属性图模型和三元存储模型，并有三种常见的声明式图查询语言: Cypher/SPARQL/Datalog。

### 4.1 属性图
属性图中，图存储看作由两个关系表组成，一个用于顶点，另一个用于边。

Cypher 是一种用于属性图的声明式查询语言，最早为 Neo4j 图数据库创建。

### 4.2 三元存储
在三元存储中，所有信息都以非常简单的三部分形式存储(主体，谓语，客体):
1. 主体相当于图的顶点
2. **客体**是以下两种:
    - 原始数据类型中的值，入字符串和数字。在这种情况下，三元组的谓语和客体分别相当于主体属性中的键和值，比如(lucy, age, 32)
    - 图中的另一个顶点，此时谓语是图中的边，主体是尾部顶点，客体是头部顶点，比如(lucy, marriedTo, alian)

SPARQL 是一种采用**RDF**数据模型的三元存储查询语言。

### 4.3 Datalog
Datalog 是比 Cypher/SPARQL 更古老的语言，Datalog 的数据模型类似于三元存储，它采用**谓语(主体，客体)**的表达式，比如 age(lucy, 33)，marriedTo(lucy, alian)。

图的模型查询语言都比较复杂，这里我们就只做非常简单的介绍，详细内容大家可以在需要时自行查阅相关文档。
