《数据密集型应用系统设计》 - 数据模型和查询语言

2022-12-06 08:53:15 浏览数 (2)

概览

  • 现实世界的API和相关程序作用于某个特定领域解决现实生活的某些问题。
  • 存储数据的模型可以使JSON也可以是XML类型。
  • 如何展示以及表示JSON,以及如何操作和处理数据模型使应用开发人员天职工作。
  • 越底层的工程师需要考虑的内容越多,需要具备过硬的软硬件知识。

NOSQL诞生

第一部分讲述了NOSQL为什么会主键由关系模型发展而来。以及介绍了历史长河中曾经被尝试的一些模型信息。

NOSQL之所以越来越受欢迎,主要是下面几个特点:

  • 相比关系型数据库更加灵活的存储形式,支持大的数据集和写入吞吐量。
  • 对于一些特定查询操作需要NOSQL完成。
  • 开源免费的产品更加受到欢迎。
  • 关系型数据固有的一些缺点,NOSQL更灵活的表现形式。

对象关系匹配问题

所谓对象和关系的匹配问题指的是在一个看似简单的现实对象中,如果通过关系型数据库往往需要较多的表之间形成关联关系才能完整展示。

而使用NOSQL数据模型,则可以直接通过一个JSON模型,展示一个对象的多种嵌套关系。

虽然ORM框架某些程度上解决了数据库数据和对象模型的映射问题,但是并不能完全解决灵活性问题,在NOSQL上不存在灵活性限制。

最终一对多的关系模型由于不匹配出现了树状结构:

多对一和多对多

多对一需要使用唯一ID进行关联,使用唯一ID的好处是一旦创建就不需要更改,本身的无意义特点也决定了不会被轻易改变的特点。

如果不使用关联,则多对一的展示需要的是多次关联查询的操作,把一个对象的内容拆分为多个查询搜索。

所以一个单体对象在最初非常适合使用单一的关系模型,而在后续得扩展之中发现对象的嵌套使用关系型数据库虽然也能完成,但是带来是臃肿和业务复杂的加剧。

显然文档模型在处理关系的层面上更加灵活。

网络模型

网络模型是由于 CODASYL 的标准化被提出,最原始的数据库数据库可以看做是不同厂商实施的CODASYL模型。

关于网络模型的历史,可以看看wiki的相关介绍:

CODASYL - Wikipedia

CODASYL属于层次模型的推广,网络模型的架构之下每个记录可能多个父节点,通过一个节点服务多个纪录,实现一对多和多对一的模型。

关系链路和关系模型的主键以及外键不同,使用的是类似链表指针串联的方式连接,多对多的关系模型,需要正确的找到“父节点”,才能再重复的数据中找到匹配结果。

网络模型仅仅是作为当时历史背景下解决有限硬件资源搜索慢的问题处理的,最大的缺点和他的特点一样,就是这个特殊的“父节点”,为了寻找一条关系链路,需要准确找到父节点,显然这种模型是复杂并且难以维护的。

CODASYLwiki解释:CODASYL - Wikipedia

关系模型

关系模型定义了表和元组(行)的集合,支持任意的条件搜索和表中主外键清晰的逻辑结构,迅速取代网络模型从而得到快速发展。

虽然关系型数据库的扩展带来的是越来越复杂的关系模型,但是关系模型的最大特点是只需要构建一次查询优化器就可以使得所有的应用程序都可以通用。最终查询优化器解决了网络模型链路查找的痛点问题。

文档模型比较

文档模型为了解决关系模型的复杂化诞生,文档模型的关系也就是外键信息被叫做文档引用,可以通过直接链接查询和解析嵌套“关系”,所以这种设计并没有遵循网络模型单一父节点的特点。

文档模型和关系模型

现今的数据和网络结构通常是文档模型和关系模型的结合,文档模型可以聚合多个关系表的内容仅限一次展示,但是如果存在多对多的关系,由于文档模型的自由一定程度上需要应用程序进行限制和防范,在这一层面上关系模型显然胜任多对多的处理。

针对关系模型的字段扩展通常需要小心谨慎的完成,比如在MYSQL种修改表alter table需要建立 新的BTree树并且进行拷贝工作,如果表非常大会非常久的停机时间。

一种处理方式是通过建立新表拷贝旧表的数据导入来完成,可以保证不受影响的情况下完成备份操作。如果需要聚合多个对象的内容,使用文档模型显然更加合适,而使用关系模型则需要维护庞大的多表结构。

查询局限性

文档模型的瓶颈出现在本身的数据结构上,尤其是JSON或者XML格式,存储和更新文档模型在文档模型较大的时候磁盘IO的开销比较大,大文档模型的查询效率也会越发效率低下。

关于文档模型的JSON以及XML优化,将在第一部分的第四章节进行更详细的讲解。

关系模型和文档模型融合

主流的数据库在文档模型的发展之后,逐渐引入了对文档模型的兼容,比如Postgresql在9.3之后引入了JSON的API以及原生JSON的存储支持,支持文本以及二进制的存储。

而在文档数据库方面同样存在反向结合关系数据模型的特点,比如MongoDB可以自动解析数据库的引用关系转化为文档模型。

目前看来最终未来两者的模型结构是融合而不是一方取代另一方的模式。

数据查询语言

声明式查询

所谓声明式查询指的是只需要数据模型以及制定结果,通过抽象转化数据以及数据显示实现这一个目标完成数据模型展示逻辑上的抽象。

换句话说声明式的查询只关注整体的规范,不关注具体的实现,但是在SQL中存在诸多限制,所以SQL也存在许多的优化空间,太过自由和不够自由是声明式查询的优点和缺点。

以此为代表的的声明式查询有下面几种结构:

  • SQL
  • WEB标签

命令式查询

命令式查询的好处是对于业务处理的灵活性十足, 相比于声明式的抽象和难以排查,命令式的查询则具备较强的逻辑性和可排查行。

当然命令式查询最大的问题是随着逻辑的复杂,命令会越发的难以阅读,如果不加以重构最终就会造成无法阅读的代码。但是不管怎么说相比较声明式语言,命令式显然更容易理解一些。

MapReduce 查询

MapReduct是一种编程模型,主要作用是在多机器上面晚餐海量数据处理,最初由Google提出。关于这一数据结构的讨论在第十章也有更为详细的讨论。

下面是可供参考的原始论文:

[[Mapreduce.pdf]](Obsidian)

[[三大论文中文版.pdf]](Obsidian)

MapReduce 查询是一种介于声明式和命令式之间的一种组件,代码片段可以被处理框架反复调用。

主要的函数分为 map 函数和 reduce 函数。例如,假设 observations 集合包含如下两个文档:

将下面的的SQL进行 MapReduce 函数操作

代码语言:javascript复制
SELECT date_trunc (’ month ’, observation_timestamp) AS sum(num_animals) AS total_animals

FRO问 observations

WHERE family= 'Sharks ’ GROUP BY observation_month ;

对于这个查询,首先需要编写相关的 mapreduce 函数对于实现上面的相同效果:

代码语言:javascript复制
db.observations.mapReduce(

function map() { @ var year = this. observation Times tamp . getFull Year(); var month = this .observationTimestamp .getMonth()   1; emit (year  ”-”  mo『1th, this . numAnimals); €>

},

function reduce(key, values) { e return Array . sum(values); ©

query: { family :” Sharks" } , 0 out :” monthlySharkReport ”@

每个文档都会调用 一次map 函数,从而产生Emit ( '’ 1995 - 12 4 )。随后, reduce 函数将被调用, reduce ( “ 1995 - 12 ” , 【3 ,4】),最终返回总和 7。

注意 在这个例子中map 以及 reduce 函数需要依赖纯JS函数来完成操作,传递进去的数据作为输入操作同时不能完成数据库查询的操作。

这些限制保证数据库查询可以在任意的位置运行函数,一旦失败重新运行即可,所以最后发现MapReduce特点是一个相当底层的编程模型,用于在计算集群上分布执行。

MapReduce的一个可用性问题是,必须编写两个密切协调的JavaScript 函数,这通常比编 写单个查询更难,此外MapReduct编写高级函数执行相关操作这比写单个查询要还要难上不少。

如果书中的句子难以理解,我们可以换用IBM官方的介绍,个人认为比较直观的显示这两个函数的意义。

类似下面的情况,如果我们要对汉堡进行分类,抽出里面的所有内容,首先我们需要用map函数把汉堡分类为一个个部分,然后再通过reduce函数对于相同分类的内容进行合并。

最终结果就像是下面这样,我们通过汉堡的归类组合,拼接出多种不一样的汉堡:

最终通过JSON的方式进行展示

对于不同函数的调用,最终实现不同形式的数据以获取不同的数据统计信息,这种类似灵活运用SQL函数的结果,运用Reduce的各种变化是非常有意义的。

图数据模型

图数据模型相比其他几个模型来说复杂很多,但是实际使用图数据库的厂商通常制定了一套图数据模型查询语言帮助开发者降低门槛。

个人始终认为图模型才是人类思考的最终形式,因为这种模型实际上更像是对于“网络模型”的变种和拓展,从图中也可以看到“父节点”本身的界限了解的更加清晰。

图的主要思想是顶点(也叫做节点或者实体)以及 边(关系和弧)进行建模。

典型的案例可以直接看下面的图形:

图模型通常具备下面的特点:

  1. 强大的灵活性, 顶点和顶点之间的关系不受限制,没有强制的措施否认事物的关联。
  2. 利于演化和扩展,编写一个关系模型受到数据模型本身的影响比较小,在扩展中复杂化数据结构对于图模型本身也具备兼容条件。

下面讨论的内容涉及了Neo4j为代表的图数据库实现,Neo4j也是市面上较为成熟的图数据库。

属性图

在属性图模型中,每个顶点包括:唯一的标识符、 出边的集合、 人边的集合、 属性的集合 (键-值对)

每个边包括 :唯一的标识符、边开始的顶点(尾部顶点) 边结束的顶点(头部顶点) 描述两个顶点间关系类型的标签、属性的集合 (键-值对)。

通过关系模型表示,属性图类似下面的语句:

属性图存在下面的特点:

  • 顶点之间的互相连接不存在限制。
  • 给定顶点可以快速的找到边和另一个顶点。

Cypher 查询语言

Cypher是一种用于属性图的声明式查询语言, 最早为Neo4j 图形数据库而创建,另外Cypher这个单词出自黑客帝国的一个比较重要的角色,这个单词的原意叫做“暗号”。

Cypher的语法结构如下,包含一个顶点和一个边,数据的存储是使用类似JSON的key/value方式。初看可能觉得奇怪,但是理解概念之后意外的十分好上手。

查询显然是根据出生地和居住地这两条关系线找到位置相关的信息,最终返回用户的名称,比较符合人的思考习惯。这里再举一个更简单点的例子,比如查找相关的迁出地的人和迁入地的人:

对于上面的查询写法并不是唯一的,其实可以直接从Location开始查找欧洲和美国两个顶点,然后根据关系线找到其他的关系也是一种解法。

Neo4j还是比较意思的东西,书中只是简单介绍了一下,更多内容可以找一些简单的项目结合官方问你大概可以快速入门和上手。

当然我不推荐你研究过深,这种东西在国外应用场景也不多见,看起来壮阔的脑图实际上用起来因人而异,至少OB软件个人不太喜欢用。

Neo4j相关阅读参考:# Neo4了解# 安装Apoc插件以及JAVA集成

SQL中的图查询

如果上面的案例中的关系使用关系型数据库实现,虽然完成起来可能很复杂但是确实是可以完成,需要大量的关系表配合完成,关系型数据库另一项难题是这样的针对“边”的检索能力十分羸弱,所以不建议去让关系型数据库去干它不擅长的东西。

SQL在遇到图数据库的冲击之后也开始了关于图查询到研究,目前较为成功的案例为PostgreSql的图查询(Graph Query)。

小贴士: 注意在SQL:l999标准以后, 查询过程中这种可变的遍历路径可以使用称为递归公用表表达式(即WITH RECURSIVE) 来表示。这里截取了一部分,但是显然这个查询复杂而且显得臃肿不可理解。

这样的查询也更加说明对于不同的场景选择合适的数据模型非常重要,不要试图去让一个不适合的数据模型强行去做另一个模型的专长,不会显得技术牛逼,只会显得白费力气。

三元存储和SAPRQL

三元存储的方式几乎和SPARQL一致。只是不同的名词采用了相同的思想,三元模型发展出了不同的工具和语言,所以这里还算要放到“图”的范畴考虑。

三元的三元分别叫做:主体、谓语、客体。有一丁点儿类似语言中到主谓宾。

在三元模型汇总主体充当图的顶点,客体分为下面两种:

  • 原始数据类型的值。这种情况可以认为谓语以及客体相当于主体的键值对的键和值。
  • 图的另一个顶点。相当于主从结构的客体部分类似另一个“顶点”,此时谓语是一条“边”。

也就是说主体必定是一个抽象的顶点“对象”,而客体可以是另一个“主体”对象,也可以是一个具体的值。

这样描述依然比较抽象,下面是举个相关的结构图例子:

为了简化书写减少相同单词的输入,可以使用分号对于这样的写法进行改良:

吐槽:图数据的介绍这一段显然有点翻译灾难,有些个人认为翻译不是很恰当,建议直接看Neo4j的使用更为直观。

语义网

语义网本质指的是将发给人类阅读的文字按照机器本身可以识别的方式解读?RDF框架实现了这样的机制,不同网站的数据合并为一个数据网络,也就是实现数据互联。

但是因为它在过去被过分的夸大,所以导致与此沾边的概念会受到牵连,注意RDF是不同的数据模型。

RDF数据模型

Turtle语言参考: https://www.w3.org/TR/turtle/

国内有博主做了翻译,很强: https://www.cnblogs.com/coodream2009/p/10320437.html

一句话Turle语言:Turtle文档是以紧凑的文本形式来描述一个RDF图,这种RDF图是由主语、谓词、宾语组成的三元组构成的。

通过Turtle语言实现代表了RDF数据的人类可读格式,目前已经有不少开源组件支持对于这种数据模型格式的转化,比如使用RDF/XML语法。

这门语言主要的目的是不同网站之间的数据河流,有一个特殊约定是对于三元结构存在主体、谓语、客体三部分通常为URL的设计,采用这样的设计是防止相同数据的冲突无法区分的问题,这时候通过URI区分是一种比较好的方式,哪怕结果相同也不会影响关系的存在。

从RDF角度看URI不一定是需要解析,也有可能是一个URI占位符号的存在。

SPARQL查询语言

定义:采用 RDF数据模型的三元存储查询语言。名字是SPARQL Protocol 和RDF Query Language的缩写 ,发音为“sparkle”。注意这要比Cypher还要早,并且后者借用了前者的模式匹配,所以不少地方比较像。下面是这门语言的相关格式:

形式和Cypher基本类似,但是RDF的区别是不分属性和边。

图数据库和网络模型的比较

主要的区别如下:

  1. 网络模型需要指定哪一条记录嵌套在其他的标记当中,图数据不存在记录嵌套关系以及记录限制,可以为需求提供更多的灵活性。
  2. 图数据库可以通过一个顶点索引不同顶点,而网络模型需要唯一的一个入口找寻关系。
  3. 图数据库顶点和边不一定是有序的,而网络模型则在插入新记录的时候考虑记录在集合中的位置。
  4. 网络模型中所有查询都是命令式,图数据库使用自制语言,可以灵活的组合顶点和边形成网络。

Datalog基础

Datalog要比SPARQL 以及Cypher更为古老,作为查询语言的鼻祖比较重要。实际案例:

  • Datomic系统的查询语言
  • Cascalog 主要是查询大数据集的Datalog实现。

Datalog 的模型类似SPASQL,其中重要的区别是它并不是使用三元而是二元结构,只是用谓语(主体、客体)的方式表达和处理。

下面为使用Datalog的语法实现上述的查询功能,注意和SPARQL以及Cypher查询语言不同的,是因为它需要每次实现一块功能。

查询逻辑类似 “树分叉匹配”的方式处理,通过“包含”关系以及二元结构递归整个二三目录产生所有的匹配结果,最终形成下面的最终结果:

在最后一级也就是第三级当中可以指定who来查找具体的人。Datalog虽然看上去比较繁琐,但是实际上非常强大,通过规则拆解关系,可以组合一级来重用各种查询,对于复杂数据的处理十分方便。

总结

数据模型是抽象并且复杂的问题,在本书的第二章重点讨论了不同数据模型的优劣,我们可以明显的看到最初的数据模型实际上是属于“网状”的。

最初人们的设想是通过层级结构和单一节点作为入口展示节点,后续则发现这种的单一结构虽然可以解决一对多,但是碰到多对多会十分复杂,这种想法很快被关系模型取代。

关系型数据库发展之后,开发人员发现对于多关系结构的组合在传统关系数据库表述并不合适,所以出现了“NOSQL”,而“NOSQL” 本身又划分出两个分歧线路:

  1. 文档数据库设定数据都是文档,文档通常是独立的,和其他文档之间关系不大。
  2. 图数据库强调节点之间的强关联,更加贴合最原始的网状模型,特点是所有数据都能产生联系。

文档数据库和图数据库的共同特点是都不会对于存储的形式加以限制,可以更快的适应需求,而关系型数据库则适用于业务逻辑的场景,在目前看来Btree为首的数据结构的关系型数据库还能活很长时间。

此外还有一些数据模型也比较有意思:

  • 基因组数据的研究人员需要智行序列相似的搜索,所以存在关于基因组的数据库软件,比如GenBank。
  • 粒子物理学家通过大型对撞机(LHC)的项目来实PB级别的数据管理,需要一些定制的解决方案避免硬件成本失控。
  • 全文搜索可以说一种警察和数据库一起使用的数据模型。

写在最后

个人认为收获比较大的是从原始到现代了解了一些数据库的不同分支,有的分支还属于战未来的阶段,而有的分支在逐渐消亡,有的分支个人也从来没听过,当然可能一辈子都没有交集,但是十分感谢作者一一讲解,但是从个人看来什么样的库能贴合商业化和产品话,谁更有可能活下来,因为数据库还是各种存储媒介,最终只有被用上才有意义和价值。

库是很复杂的概念,大部分程序员不要妄想去深钻数据库,很可能产生整个计算机都要重修失去自信心,了解到原理的部分点到为止即可,或者说能用它拧出更好的螺丝都并不是非常容易。

0 人点赞