【读书笔记】《数据密集型应用系统设计》第2章 数据模型与查询语言
复杂的应用程序可能会有更多的中间层,例如基于API来构建上层API ,但是基本思想相同:每层都通过提供一个简洁的数据模型来隐藏下层的复杂性。这些抽象机制使得不同的人群可以高效协作。
一、关系模型与文档模型
现在最著名的数据模型可能是SQL ,它基于Edgar Codd于1970年提出的关系模型: 数据被组织成关系(relations),在SQL中称为表(table),其中每个关系都是元组 (tuples)的无序集合(在SQL中称为行)。
1.1 NoSQL的诞生
进入21 世纪,NoSQL成为推翻关系模式主导地位的又一个竞争者。现在很多新兴的数据库系统总是会打上NoSQL的标签,而其含义也已经被逆向重新解释为“不仅仅是SQL”。
采用NoSQL数据库有这样几个驱动因素 ,包括:
-
-
比关系数据库更好的扩展性需求,包括支持超大数据集或超高写入吞吐量。
-
普遍偏爱免费和开源软件而不是商业数据库产品 。
-
关系模型不能很好地支持一些特定的查询操作。
-
对关系模式一些限制性感到沮丧,渴望更具动态和表达力的数据模型。
-
不同的应用程序有不同的需求,某个用例的最佳的技术选择未必适合另一个用例。因此,在可预见的将来,关系数据库可能仍将继续与各种非关系数据存储一起使用,这种思路有时也被称为混合持久化。
1.2 对象-关系不匹配
现在大多数应用开发都采用面向对象的编程语言,由于兼容性问题,普遍对SQL数据模型存在抱怨:如果数据存储在关系表中,那么应用层代码中的对象与表、行和列的数据库模型之间需要一个笨拙的转换层。
例如,图 2-1 展示了如何在关系模式中表示简历(类似Linkedln profile)。整个简历可以通过唯一的标识符user id来标识。像first name和last name这样的字段在每个用户中只出现一次,所以可以将其建模为Users表中的列。然而,大多数人在他们的职业(职位)中有一个以上的工作,并且可能有多个教育阶段和任意数量的联系信息。 用户与这些项目之间存在一对多的关系,可以用多种方式来表示:
-
在传统的SQL模型(SQL : 1999之前)中,最常见的规范化表示是将职位、教育和联系信息放在单独的表中,并使用外键引用 users表,如图 2-1 所示。
-
之后的SQL标准增加了对结构化数据类型和XML数据的支持。这允许将多值数据存储在单行内,井支持在这些文档中查询和索引。 Oracle 、 IBM DB2 、 MS SQL Server和PostgreSQL都不同程度上支持这些功能。一些数据库也支持JSON数据类型,例如IBM DB2 、MySQL和PostgreSQL。
-
第三个选项是将工作、教育和联系信息编码为JSON或XML文档,将其存储在数据库的文本列中,并由应用程序解释其结构和内容。对于此方式,通常不能使用数据库查询该编码列中的值。
对于像简历这样的数据结构,它主要是一个自包含的文档( document ),因此用JSON表示非常合适,参见示例2-1。与XML相比, JSON的吸引力在于它更简单。 面向文档的数据库(如MongoDB、 RethinkDB、 CouchDB和Espresso )都支持该数据模型。
JSON表示比图 2-1的多表模式具有更好的局部性。如果要在关系模式中读取一份简历,那么要么执行多个查询(通过userid查询每个表),要么在users表及其从属表之间执行混乱的多路联结。而对于JSON表示方法,所有的相关信息都在一个地方,一次查询就够了。用户简历到用户的职位、教育历史和联系信息的一对多关系,意味着数据存在树状结构, JSON表示将该树结构显式化(见图 2-2 )。
1.3 多对一与多对多的关系
无论是存储ID还是文本字符串,都涉及内容重复的问题。当使用ID时,对人类有意义的信息(例如慈善这个词)只存储在一个地方,引用它的所有内容都使用ID (ID只在数据库中有意义)。当直接存储文本时,则使用它的每条记录中都保存了一份这样可读信息。
使用ID的好处是,因为它对人类没有任何直接意义,所以永远不需要直接改变 :即使ID标识的信息发生了变化,它也可以保持不变。
任何对人类有意义的东西都可能在将来某个时刻发生变更。如果这些信息被复制 ,那么所有的冗余副本也都需要更新。这会导致更多写入开销,并且存在数据不一致的风险(信息的一些副本被更新,而其他副本未更新)。消除这种重复正是数据库规范化的核心思想。
然而这种数据规范化需要表达多对一的关系(许多人生活在同一地区,许多人在同一行业工作),这并不是很适合文档模型。对于关系数据库,由于支持联结操作,可以很方便地通过ID来引用其他表中的行。而在文档数据库中,一对多的树状结构不需要联结,支持联结通常也很弱。如果数据库本身不支持联结,则必须在应用程序代码中,通过对数据库进行多次查询来模拟联结,联结的工作其实从数据库转移到了应用层。
即使应用程序的初始版本非常适合采用无联结的文档模型 , 但随着应用支持越来越多的功能,数据也变得更加互联一体化。
1.4 文档数据库是否在重演历史?
20世纪70年代最受欢迎的商业数据处理数据库是IBM信息管理系统,最初是为了阿波罗太空计划中 的库存管理而开发的,井于1968年首次商业发布,至今它仍在维护和使用。
IMS采用了相当简单的数据模型,称为层次模型,它与文档数据库使用的JSON模型有一些显著的相似之处。它将所有数据表示为嵌套在记录中的记录(树)。
和文档数据库类似,IMS可以很好地支持一对多关系,但是它支持多对多关系则有些困难,而且不支持联结。开发人员必须决定是复制(反规范化)多份数据,还是手动解析记录之间的引用。 20世纪六七十年代的这些问题与开发员今天遇到的文档数据库的问题非常相似。
为了解决层次模型的局限性,之后又提出了多种解决方案。其中最著名的是关系模型( relational model ,后来演变成为SQL ,井被广泛接受)和网络模型( network model ,最初有很多拥趸,可惜最终被人们淡忘)。
1.4.1 网络模型
网络模型由一个称为数据系统语言会议的委员会进行标准化,井由多个不同的数据库厂商实施,它也被称为CODASYL模型。CODASYL模型是层次模型的推广。在层次模型的树结构中,每个记录只有一个父结点;而在网络模型中, 一个记录可能有多个父结点。
在网络模型中,记录之间的链接不是外键,而更像是编程语言中的指针(会存储在磁盘上)。访问记录的唯一方法是选择一条始于根记录的路径 , 并沿着相关链接依次访问。这条链路也因此被称为访问路径。
在最简单的情况下,访问路径像是遍历链表:从链表的头部开始,一次查看一个记录,直到找到所需的记录。但是在一个多对多关系的世界里,存在多条不同的路径通向相同的记录,使用网络模型的程序员必须在脑海中设法跟踪这些不同的访问路径。
CODASYL中的查询通过遍历记录列表,并沿着访问路径在数据库中移动游标来执行。如果记录有多个父结点(即来自其他记录的多个传入指针), 则应用程序代码必须跟踪所有关系。 甚至CODASYL委员会的成员也承认, 这就像在一个n维数据空间中进行遍历。
最大的问题在于它们使查询和更新数据库变得异常复杂而没有灵活性。无论是层次模型还是网络模型,如果脱离数据的访问路径,那么将寸步难行。它也支持改变访问路径,但随之需要大量的手写数据库查询代码,重新实现处理新的访问路径。总之,对应用程序的数据模型进行更改是一件非常困难的事情。
1.4.2 关系模型
相比之下,关系模型所做的则是定义了所有数据的格式:关系(表)只是 元组(行) 的集合 ,仅此而已。 没有复杂的嵌套结构,也没有复杂的访问路径。可以读取表中的任何一行或者所有行,支持任意条件查询。可以指定某些列作为键来匹配这些列来读取特定行。可以在任何表中插入新行,而不必担心与其他表之间的外键关系。
在关系数据库中, 查询优化器自动决定以何种顺序执行查询 ,以及使用哪些索引。关系数据库的查询优化器称得上是一个复杂的怪兽,研究开发人员多年来持续投入,花费巨大。不管怎样,关系模型的一个核心要点是:只需构建一次查询优化器,然后使用该数据库的所有应用程序都可以从中受益。
1.4.3 文档数据库的比较
在表示多对一和多对多的关系时,关系数据库和文档数据库并没有根本的不同:在这两种情况下,相关项都由唯一的标识符引用,该标识符在关系模型中被称为外键,在文档模型中被称为文档引用。标识符可以查询时通过联结操作或相关后续查询来解析。迄今为止,文档数据库井未遵循CODASYL标准。
1.5 关系数据库与文档数据库的现状
本章只关注数据模型的差异。
支持文档数据模型的主要论点是模式灵活性,由于局部性而带来较好的性能 ,对于某些应用来说,它更接近于应用程序所使用的数据结构。关系模型则强在联结操作、多对一和多对多关系更简洁的表达上,与文档模型抗衡。
1.5.1 哪种数据模型的应用代码更简单?
如果应用数据具有类似文档的结构(即一对多关系树,通常一次加载整个树), 那么使用文档模型更为合适。文档模型也有一定的局限性 :例如,不能直接引用文档中的嵌套项。但是在文档数据库中,对联结的支持不足是否是问题取决于应用程序,例如,在使用文档数据库记录事件发生时间的应用分析程序中,可能永远不需要多对多关系。
通常无法一慨而论哪种数据模型的应用代码更简单。这主要取决于数据项之间的关系类型。对于高度关联的数据,文档模型不太适合,关系模型可以胜任,而图模型则是最为自然的。
1.5.2 文档模型中的模式灵活性
大多数文档数据库,以及关系数据库中的JSON支持,都不会对文档中的数据强制执行任何模式。没有模式意味着可 以将任意的键值添加到文档中,并且在读取时,客户端无法保证文档可能包含哪些字段。
文档数据库有时被称为无模式,但这具有误导性,因为读数据的代码通常采用某种结构因而存在某种隐形模式,而不是由数据库强制执行。更准确的术语应该是读时模式(数据的结构是隐式的,只有在读取时才解释),与写时模式(关系数据库的一种传统方法,模式是显式的,并且数据库确保数据写入时都必须遵循)相对应。
在某些情况下,模式带来的损害大于它所能提供的帮助,无模式文档可能是更自然的数据模型。但是,当所有记录都有相同结构时,模式则是记录和确保这种结构的有效机制。
1.5.3 查询的数据局部性
文档通常存储为编码为JSON 、XML或其二进制变体(如MongoDB的BSON )的连续字符串。如果应用程序需要频繁访问整个文档(例如,在网页上呈现它),则存储局部性具有性能优势。
局部性优势仅适用需要同时-访问文档大部分内容的场景。由于数据库通常会加载整个文档,如果应用只是访问其中的一小部分,则对于大型文档数据来讲就有些浪费。对文档进行更新时,通常会重写整个文档,而只有修改量不改变源文档大小时, 原地覆盖更新才更有效。因此,通常建议文档应该尽量小且避免写入时增加文档大小。这些性能方面的不利因素大大限制了文档数据库的适用场景。
1.5.4 文档数据库与关系数据库的融合
随着时间的推移,似乎关系数据库与文档数据库变得越来越相近,或许这是一件好事 :数据模型可以相互补充的。如果数据库能够很好处理文档类数据,还能对其执行关系查询,那么应用程序可以使用最符合其需求的功能的组合。
融合关系模型与文档模型是未来数据库发展的一条很好的途径。
二、数据查询语言
命令式语言告诉计算机以特定顺序执行某些操作。你完全可以推理整个过程,逐行遍历代码、评估相关条件、更新对应的变量,并决定是否再循环一遍。
而对于声明式查询语言(如SQL或关系代数),则只需指定所需的数据模式,结果需满足什么条件,以及如何转换数据(例如,排序、分组和聚合),而不需指明如何实现这一目标。数据库系统的查询优化器会决定采用哪些索引和联结,以及用何种顺序来执行查询的各个语句。
声明式查询语言很有吸引力,它比命令式API更加简洁和容易使用。但更重要的是,它对外隐藏了数据库引擎的很多实现细节,这样数据库系统能够在不改变查询语句的情况下提高性能。
声明式语言通常适合于并行执行。现在CPU主要通过增加核,而不是通过比之前更高的时钟频率来提升速度。而命令式代码由于指定了特定的执行顺序,很难在多核和多台机器上并行化。
2.1 Web上的声明式查询
略,主要为了展示CSS声明式查询比JavaScript命令式操作好的多。
2.2 MapReduce查询
MapReduce是一种编程模型,用于在许多机器上批量处理海量数据,兴起于Google。一些NoSQL存储系统(例如 Mongo DB和Couch DB)支持有限的MapReduce方式在大量文档上执行只读查询。
MapReduce既不是声明式查询语言,也不是一个完全命令式的查询API,而是介于两者之间:查询的逻辑用代码片段来表示,这些代码片段可以被处理框架重复地调用。它主要基于许多函数式编程语言中的map(也称为collect )和reduce(也称为fold或inject )函数。
map和reduce函数对于可执行的操作有所限制。它们必须是纯函数,这意味着只能使用传递进去的数据作为输入,而不能执行额外的数据库查询,也不能有任何副作用。这些限制使得数据库能够在任何位置、以任意顺序来运行函数,并在失败时重新运行这些函数。不管怎样,该功能非常强大,可以通过它来解析字符串、调用库函数、执行计算等。
请注意,SQL并没有任何限制规定它只能在单个机器上运行,而MapReduce也并非垄断了分布式查询。能够在查询中使用JavaScript代码是一种高级查询特性,它也不限于MapReduce ,某些SQL数据库也可以扩展使用 JavaScript 函数。
MapReduce的一个可用性问题是,必须编写两个密切协调的JavaScript函数,这通常比编写单个查询更难。此外, 声明式查询语言为查询优化器提供了更多提高查询性能的机会。由于这些原因,MongoDB 2.2增加了称为聚合管道的声明式查询语言的支持。聚合管道在表达能力上相当于SQL的子集 ,但是它使用了基于JSON的语法,而不是SQL的英语句式语法。
三、图状数据模型
关系模型能够处理简单的多对多关系, 但是随着数据之间的关联越来越复杂,将数据建模转化为图模型会更加自然。
图由两种对象组成:顶点(也称为结点或实体)和边(也称为关系或弧)。很 多数据
可以建模为图。典型的例子包括 :
社交网络
顶点是人,边指示哪些人彼此认识 。
Web图
顶点是网页,边表示与其他页面的HTML链接。
公路或铁路网
顶点是交叉路口,边表示他们之间的公路或铁路线。
在刚才给出的示例中, 图的顶点表示相同类型的事物(分别是人、网页或交叉路 口)。 然而,图井不局限于这样的同构数据, 图更为强大的用途在于, 图提供了单个数据存储区中保存完全不同类型对象的一致性方式。例如, Facebook维护了一个包含许多不同类型的顶点与边的大图:顶点包括人、地点、事件、 签到和用户的评论。
浙公网安备 33010602011771号