概要
本文主要围绕两个主要的概念来展开:
- 如何分析一个数据模型:
-
基本考察点:数据基本元素和元素之间的对应关系(一对多、多对多)
-
比较几种的常用模型:关系模型(最为流行),文档模型(树状),图模型(极大自由度)
-
schema模式:强Schema(写时约束),弱Schema(读时解析)
- 如何考量查询语言:
-
如何与数据模型关联、匹配
-
声明式和命令式
数据模型
数据模型的核心点在于:如何组织数据,如何标准化关系,如何关联现实。它既决定了我们构建软件的方式(实现),也左右了我们看待问题的角度(认知)
作者开篇以计算机的不同抽象层次来让读者对泛化的数据模型有个整体观感。大多数应用都是通过不同的数据模型层级累进构建的。
每层模型的核心问题式:如何用下一层的接口来对本层进行建模?
-
作为应用开发者,需要将现实中的具体问题抽象为一组对象、数据结构(data structure)以及作用于其上的API
-
作为数据库管理员(DBA),为了持久化上述数据结构,你需要将他们表达为通用的数据模型(data model),如文档数据库中的XML/JSON、关系数据库中的表、图数据库中的图。
-
作为数据库系统开发者,需要将上述数据模型组织为内存中、硬盘中或是网络中的字节(Bytes)流,并提供多种操作数据集合的方法。
-
作为硬件工程师,你需要将字节流表示为二极管的电位(内存)、磁盘中的磁极(磁盘)、光纤中的光信号(网络)
在每一层,通过对外暴露简洁的数据模型,我们隔离和分解了现实世界的复杂度。
这也反过来说明了,好的数据模型需有两个特点:
简洁直观和具有组合性
关系模型与文档模型
关系模型
关系模型式当今最流行的数据库模型,统指这一类数据库,特点如下:
-
将数据以关系呈现给用户(比如:一组包含行列的二维表)
-
提供操作数据集合的关系算子
常见分类:
-
事务型(TP):银行交易、火车票
-
分析型(AP):数据报表、监控表盘
-
混合型(HTAP)
关系模型诞生很多年后,虽然不时有各种挑战者,比如上世纪七八十年代的网状模型和层次模型,但始终仍未有根本的能撼动其地位的新模型。直到近十年来,随着移动互联网的普及,数据爆炸性增长,各种处理需求越来越精细化,催生了数据模型的百花齐放。
NoSQL的诞生
NoSQL(Non-SQL,后来有人转解为Not only SQL),是对不同于传统的关系数据库的数据库管理系统的统称。根据DB-Engines Ranking - popularity ranking of database management systems ,现在最受欢迎的NoSQL前几名为:MongoDB、Redis、ElasticSearch、Cassandra。
催动因素有:
-
处理更大数据集:更强伸缩性、更高吞吐量
-
开源免费的兴起:冲击了原来把握在厂商的标准
-
特化的查询操作:关系数据库难以支持的,比如图中的多跳分析
-
表达能力更强:关系模型约束太严,限制太多
面向对象和关系模型的不匹配
目前大多数应用程序开发都使用面向对象的编程语言来开发,这导致了对 SQL 数据模型的普遍批评:如果数据存储在关系表中,那么需要一个笨拙的转换层,处于应用程序代码中的对象和表,行,列的数据库模型之间。模型之间的不连贯有时被称为 阻抗不匹配
ORM框架可以帮我们解决这个事情,但是仍然不太方便。
换另一个角度来说,关系模型很难直观的表示一对多的关系。比如简历上,一个人可能有多段教育经历和多段工作经历。
文档模型:使用JSON和XML的天然嵌套
关系模型:使用SQL模型就将职位、教育单独拿一张表,然后在用户表中使用外键关联。
在简历的例子里,文档模型还有几个优势:
-
模式灵活:可以动态增删字段,如工作经历
-
更好的局部性:一个人的所有属性被集中访问的同时,也被集中存储。
-
结构表达语义:简历与联系信息、教育经历、职业信息等隐含一对多的树状关系可以被JSON的树状结构明确表达出来。
多对一和多对多
是一个对比各种数据模型的切入角度。
region在存储时,为什么不直接存储纯字符串:“Greater Seattle Area”,而是先存为region_id -> region name,其他地方都引用region_id?
-
统一样式:所有用到相同概念的地方都有相同的拼写和样式
-
避免歧义:可能有同名地区
-
易于修改:如果一个地区改名了,我们就不用去注意修改所有引用他的地方
-
本地化支持:如果翻译成其他语言,可以只翻译名字表
-
更好搜索:列表可以关联地区,进行树形组织
类似的概念还有:面向抽象编程,而非面向细节。
关于用ID还是用文本,作者提到了一点:ID对人类是无意义的,无意义的意味着不会随着现实世界的将来的改变而改动。
这在关系数据库表设计时需要考虑,即如何控制冗余,会有几种范式来消除冗余。
文档型数据库很擅长处理一对多的树形关系,却不擅长处理多对多的图形关系。如果其不支持Join,则处理多对多关系的复杂度就从数据库侧移动到了应用侧。
如,多个用户可能在一个组织工作过,如果我们想找出同一个学校和组织工作过的人,如果数据库不支持Join,则需要在应用侧进行循环遍历来Join。
文档 vs 关系
-
对于一对多关系,文档型数据库将嵌套数据放在父节点中,而非单拎出来放另外一张表
-
对于多对一和多对多关系,本质上,两者都是使用外键(文档引用)进行索引。查询时需要进行join或者动态跟随。
文档模型是否在重复历史?
层次模型
20世纪70年代,IBM的信息管理系统IMS。
几个要点:
-
树形组织,每个子节点只允许有一个父节点
-
节点存储数据,节点有类型
-
节点之间类似指针方式连接
可以看出,它与文档模型很像,也因此很难解决多对多的关系,并且不支持Join。
为了解决层次模型的局限性,人们提出了各种解决方案,最突出的是:
关系模型和网状模型
网状模型
network model是hierarchical model 的一种扩展:允许一个节点有多个父节点。它被数据系统语言会议的委员会进行了标准化,因此也被称为CODASYL模型。
多对一和多对多都可以由路径来表示。访问记录的唯一方式是顺着元素和链接组成的链路进行访问,这个链路叫访问路径。难度犹如在n-维空间中进行导航。
内存有限,因此需要严格控制遍历路径,并且需要事先知道数据库的拓扑结构,这就意味着得针对不同应用写大量的专用代码。
关系模型
在关系模型中,数据被组织成元组(tuples),进而集合成关系(relations);在SQL中分别对应行(rows)和表(tables)。
表只是一种实现,关系的说法来自集合论,指的是几个集合的笛卡尔积的子集。 R ⊆ (D1×D2×D3 ··· ×Dn) (关系用符号 R 表示,属性用符号 Ai 表示,属性的定义域用符号 Di 表示)
其主要目的和贡献在于提供了一种声明式的描述数据和构建查询的方法。即,相比网络模型,关系模型的查询语句和执行路径相解耦,查询优化器(Query Optimizer 自动决定执行顺序、要使用的索引),即将逻辑和实现解耦。
举个例子:如果想使用你的方式对你的数据集进行查询,你只需要在新的字段上建立一个索引。那么在查询时,你并不需要改变你的用户代码,查询优化器便会动态的选择可用索引。
文档型 vs 关系型
根据数据类型来选择数据模型
文档型 | 关系型 | |
---|---|---|
对应关系 | 数据有天然的一对多、树形嵌套关系,如简历。 | 通过外键 + Join 可以处理 多对一,多对多关系 |
代码简化 | 数据具有文档结构,则文档模型天然合适,用关系模型会使得建模繁琐、访问复杂。但不宜嵌套太深,因为只能手动指定访问路径,或者范围遍历 | 主键,索引,条件过滤 |
Join 支持 | 对 Join 支持的不太好 | 支持的还可以,但 Join 的实现会有很多难点 |
模式灵活性 | 弱 schema,支持动态增加字段 | 强 schema,修改 schema 代价很大 |
访问局部性 | 1. 一次性访问整个文档,较优 2. 只访问文档一部分,较差 | 分散在多个表中 |
对于高度关联的数据集,使用文档型表达比较奇怪,使用关系型可以接受,使用图模型最自然。
文档模型中Schema的灵活性
说文档型数据库是schemaless不太准确,更贴切的应该是schema-on-read
数据模型 | 编程语言 | 性能 & 空间 | ||
---|---|---|---|---|
schema-on-read | 写入时不校验,而在读取时进行动态解析。 | 弱类型 | 动态,在运行时解析 | 读取时动态解析,性能较差。写入时无法确定类型,无法对齐,空间利用率较差。 |
schema-on-write | 写入时校验,数据对齐到 schema | 强类型 | 静态,编译时确定 | 性能和空间使用都较优。 |
文档型数据库使用场景特点:
- 有多种类型的数据,但每个放一张表又不合适。
- 数据类型和结构由外部决定,你没办法控制数据的变化。
查询时的数据局限性
如果你同时需要文档中所有内容,把文档顺序存储,访问效率比较高。
但是如果你只需要访问文档中的某些字段,则文档仍需要将文档全部加载出。但是运用这种局限性不局限于文档型数据库。不同的数据库,会针对不同场景,调整数据物理分布以适用常用访问模式的局限性。
- Spanner 中允许表被声明为嵌入到父表中——常用关联内嵌(获得类似文档模型的结构)
- HBase 和 Cassandra 使用列族来聚集数据——分析型
- 图数据库中,将点和出边存在一个机器上——图遍历
关系型和文档型的融合
- MySQL 和 PostgreSQL 开始支持 JSON 原生支持 JSON 可以理解为,MySQL 可以理解 JSON 类型。如 Date 这种复杂格式一样,可以让某个字段为 JSON 类型、可以修改 Join 字段的某个属性、可以在 Json 字段中某个属性建立索引。
- RethinkDB 在查询中支持 relational-link Joins
科德(Codd):nonsimple domains,记录中的值除了简单类型(数字、字符串),还可以一个嵌套关系(表)。这很像 SQL 对 XML、JSON 的支持。