购买
下载掌阅APP,畅读海量书库
立即打开
畅读海量书库
扫码下载掌阅APP

3.1 数据库的类型

应用程序的所有持久性数据都应当存于某个数据库中。正如前文所述,数据是一切应用程序中最关键的部分,正确地处理数据对于确保项目的可行性尤为重要。

从技术上讲,数据库是数据本身的集合,由 DBMS (DataBase Management System,数据库管理系统)处理,它可以完成数据的输入和输出。通常情况下,“数据库”这个词既指数据集合,也指管理系统,具体含义取决于上下文。大多数DBMS允许访问所含的多个同类型数据库,但基于实现数据在逻辑上分离的考虑,不能在各数据库之间交叉连接。

在软件系统发展历程的大部分过程中,数据库都是关键性的工具。它们创建了一个抽象层,允许访问数据,而不必过多地关心硬件设备是如何对数据进行组织的。大多数数据库可以自行定义数据的结构,而不必担心其幕后实现过程。

正如我们在第2章所讨论的,这种抽象并不完美,有时我们不得不了解数据库的内部结构,以提高性能或以“有效的方式”做事。

DBMS是软件系统中投资最多且最成熟的项目之一。每个DBMS都有自己的独有特性,以至于有一种专门针对“数据库专家”的工作职位: DBA (DataBase Administrator,数据库管理员)。

在很长一段时间内,DBA的岗位在市场上非常受欢迎,且需要高度专业化的工程师,DBA在工作中专门负责某种特定的DBMS。作为数据库系统的专家,DBA既要掌握如何访问数据库,又要确保对数据库所做的任何调整都能让系统充分地发挥作用。他们通常是唯一被允许更改或维护数据库的人。

硬件和软件的性能改进,以及用于处理数据库复杂性的外部工具的发展,使得DBA的角色不再那么常见,尽管还存在于某些机构中。在某种程度上,系统架构师的角色取代了DBA的部分职责,虽然架构师的角色在工作中更侧重于监督而非守门人。

市场上有多种DBMS,有高质量的开源软件供选择,可支持大多数的应用场景。简单来说,我们可以把当前可用的DBMS大致分为以下几类:

关系数据库 (relational database):数据库中默认的标准。使用SQL查询语言,且有着定义的模式。例如,类似MySQL或PostgreSQL这样的开源软件,或者像Oracle或MS SQL Server这样的商业产品。

非关系数据库 (non-relational database):非传统的新型、多样化的数据库系统,包括特性各不同的产品,如MongoDB、Riak或Cassandra。

小型数据库 (small database):这类数据库的定位是嵌入系统中使用,最著名的例子是SQLite。

下面让我们更深入地了解这些数据库系统。

3.1.1 关系数据库

关系数据库又称为关系型数据库,这是最常见的数据库类型,也是提到数据库时首先就会想到的。数据库的关系模型是在20世纪70年代建立的,它的基础是创建一系列可以相互关联的表(table)。自20世纪80年代以来,关系数据库已经变得非常普及。

关系数据库中定义的每个表都有一些固定的字段(field)或列(column),数据被表述为记录(record)或行(row)。表的容量在理论上是无限的,所以可以添加越来越多的行到表中。其中的某一列被定义为主键(primary key),用于唯一地描述每行记录。因此,主键的内容在表中必须是独一无二的。

如果有某种足够独特且具备描述性的值,则它可以用于主键,这种称为自然键(natural key)。自然键也可以是字段的组合,尽管这种做法会影响其易用性。当自然键不可用时,数据库可以直接采用一个递增的计数器,以确保它在每一行都是唯一的,这种称为代理键(surrogate key)。

在需要时,主键可以用来在其他表中引用该记录,这样就建立了表间的关系。当表中的某列引用了另一个表时,该列就称为外键(foreign key)。

通过这些引用可以建立一对一的关系;或当某条记录引用了另一个表中的多条记录时,可建立一对多的关系;或者还可建立多对多的关系,此时需要一个中间表来实现交叉连接。

所有这些信息都需要在数据库的模式(schema)中进行定义。模式描述了数据库包含的每个表,每个表的字段和类型,以及它们之间的关系。

关系数据库中的关系实际上是约束。这意味着,如果数据还在某处被引用,它就不能被删除。

关系数据库有着严谨的数学背景,尽管它是以不同程度的约束来实现的。

需要注意的是,定义模式需要提前规划,并考虑到后续可能会进行的调整。在录入数据之前定义字段类型时,也需要意识到后续可能要做的调整。虽然模式可以再修改,但这毕竟是一个敏感操作,如果不注意的话,可能会导致数据库在一段时间内无法访问,在最糟糕的情况下,数据甚至会出现错误或不一致的情况。

还可以在数据库上执行查询操作,以搜索符合设定条件的数据。为此,可以根据表的关系将其连接起来。

几乎所有的关系数据库都是使用SQL(Structured Query Language,结构化查询语言)来进行交互的。这种语言已经成为与关系数据库配套使用的标准,并同样遵循这里所说的概念。SQL语言既描述了如何查询数据库,又描述了如何添加或修改其数据。

SQL最主要的特点在于,它是一种声明式(declarative)的语言。这意味着它的语句描述的是结果,而不是像典型的命令式(imperative)语言那样描述获取结果的过程。SQL语言关注于“做什么”,从而把内部实现细节从“怎么做”中抽象出来。

命令式语言描述控制流,是最常见的语言。命令式语言的例子有Python、JavaScript、C和Java。声明式语言通常被限制在特定的领域,即领域特定语言(Domain-Specific Language,DSL),可以用更简单的术语描述结果,而命令式语言则更加灵活。

这一特点使得SQL在系统之间可以移植,因为在不同的数据库中,同样的SQL操作,其内部实现过程可能是不同的。在使用特定的某个关系数据库时,再去匹配其他数据库相对会比较容易。

这种特性有时被用于测试本地数据库,它与系统生产环境最终所用的数据库不同。在部分Web框架中这种测试是可行的,但有些情况需要注意,因为复杂的系统有时必须使用特定数据库的独有特性,因而无法进行这种简单的替换。

虽然关系数据库非常成熟和灵活,并被用于很多不同的场景,但也有两方面的主要问题难以处理。一方面需要一个预设的数据模式,正如前文所述。另一方面,更严重的问题是,当数据量达到一定规模后关系数据库难以处理。关系数据库被当作系统中用于提供服务的中心访问节点,一旦达到纵向扩展(vertical scaling,亦称垂直扩展)的极限,系统规模的增长就需要一些复杂的技术来实现。

本章后续内容中,我们将讨论处理该问题的具体技术,以提高关系数据库系统的可扩展性。

3.1.2 非关系数据库

非关系数据库是一类不符合关系范式的、多样化的数据库管理系统。

非关系数据库也被称为NoSQL,强调了其SQL语言的关系特性,意味着“Not(非)SQL”或“Not Only(不仅是)SQL”,以进一步反映出相比SQL其功能的增强而非减弱。

虽然在关系数据库问世之前就已经有了非关系数据库,但自21世纪以来,业界一直在引入或寻求非关系数据库的替代方案和设计。它们中的大多数旨在解决关系数据库的两个主要弱点,即其数据局限性和可扩展性问题。

非关系数据库系统的种类繁多,结构迥异,但最常见的有以下几类:

❍键值存储型数据库

❍文档存储型数据库

❍宽列数据库

❍图数据库

让我们来逐一了解这几种类型的非关系数据库系统。

键值存储型数据库

就其功能而言,键值存储型(key-value store)数据库可以说是所有数据库中最简单的。定义一个键,用于存储对应的值。这个值对系统来说是完全不透明的,不能以任何方式进行查询。在某些键值存储型数据库的实现中,甚至没办法查询系统中的键,取而代之的是,这些键需要某些操作来输入。

这种数据库与散列表或字典非常相似,但规模更大。缓存系统通常是基于这种数据库来存储数据的。

虽然技术上相似,但高速缓存(cache)和数据库之间有一个重要的区别。缓存是一种用于存储已经计算过的数据的系统,以加快其检索速度,而数据库则用于存储原始数据。如果数据不在缓存中,那么可以到不同的系统中去检索,但如果数据不在数据库中,要么是因为数据未被存储,要么就是系统出现了严重故障。因此,缓存倾向于仅在内存中存储信息,它对系统重启或故障有更强的容错能力,能轻松应对这些情况。没有缓存,系统依然可以运转,只是速度较慢而已。很重要的是,信息最终不应存储在缺乏有效备份机制的缓存系统中。这种错误偶尔会出现,比如,对临时数据来说,其风险在于,如果系统在特定时刻出了故障,就会导致数据丢失,所以需要注意此类问题。

键值存储型数据库的主要优点是:基于其简单的机制,可以快速存储和检索数据;系统还能横向扩展到很大的规模。由于每个键都是独立的,它们甚至可以存储在不同的服务器中。也可以在系统中采用冗余机制,为每个键和值存储多个副本,尽管这样会降低信息的检索速度,因为需要对多个副本进行比对以检测数据损坏。

典型的键值存储型数据库系统是Riak和Redis(如果使用时启用了持久性)。

文档存储型数据库

文档存储型(document store)数据库基于“文档”的概念,它类似于关系数据库中的“记录”。不过,文档更灵活,因为它不需要遵循预定义的数据格式。它们通常还允许在子字段中嵌入更多的数据,这是关系数据库通常做不到的,对于类似的需求,关系数据库需要创建一个关系并将相关数据存储在不同的表中。

例如,某个文档可能是这样的,以JSON格式表示如下:

文档通常以分组的形式存放在集合(collection)中,集合类似于关系数据库中的“表”。一般情况下,文档是通过作为主键的唯一ID来进行检索的,但也可以构建查询以便搜索在文档中创建的字段。

因此,在这个例子中,我们可以检索键(ID)ABCDEFG,就像在键值存储型数据库中一样,或者执行更复杂的查询,例如“在 detectives (侦探)集合中获取所有address.country等于UK的条目”。

请记住,虽然从技术上讲可以创建一个具有完全独立和不同格式的文档的集合,但在实践中,一个集合中的所有文档通常都基于某种类似的格式,有可选的字段或嵌入的数据。

集合中的文件可以通过它们的ID与其他集合中的文件进行关联,形成一个引用(reference),但通常这些数据库不能创建连接查询。同时,应用层可以检索这种关联信息。

一般来说,文档倾向于嵌入信息而不是创建引用。这可能会导致信息的非规范化,造成在多处存在重复信息。我们将在本章后面继续讨论数据非规范化问题。

典型的文档存储型数据库系统是MongoDB(https://www.mongodb.com/)和Elastic-search(https://www.elastic.co/elasticsearch/)。

宽列数据库

宽列数据库(wide-column database)用列分隔、组织其数据,并通过某些可选列来创建表。宽列数据库也不能将表中的记录与其他表进行关联。

与纯粹的键值存储型数据库相比,宽列数据库的查询功能要强一些,但需要更多的前期设计工作,以确定系统中哪些类型的查询是允许的。这比面向文档的存储限制更多,后者在设计完成后更加灵活。

通常情况下,各列的数据是有关联的,只能以特定的顺序进行查询。举个例子,比如数据库存在A、B、C三列,查询某行数据时,可以按A、A、B,或者A、B、C进行查询,但不能仅查询C列,或仅查询B、C两列。

宽列数据库定位于具有高可用性和副本数据的、规模非常大的数据库部署。典型的宽列数据库系统是Apache Cassandra(https://cassandra.apache.org/)和Google的Bigtable(https://cloud.google.com/bigtable)。

图数据库

图数据库(graph database)又称为图形数据库。前述非关系数据库都放弃了在元素之间建立关系的能力,基于此获得其他功能(如可扩展性或灵活性),而图数据库则采取了相反的思路。它极大增强了在元素之间建立关系的功能,因而可以创建复杂的图结构。

图数据库存储的对象是节点(node)和边(edge),或节点之间的关系。节点和边都可以用属性(property)来更好地描述。

图数据库的查询功能可实现根据关系检索信息,如图3-1所示。例如,给定一个公司和供应商的列表,在某个特定公司的供应链中,是否有哪些供应商在指定的国家?上到多少个层次?这类问题对于关系数据库中的第一层(获得公司的供应商和他们所属的国家)来说可能很容易解决,但对于第三层关系的数据查询来说则相当复杂且费神。

图3-1 图数据库的典型数据示例

图数据库通常用于社交图谱,即人们或组织之间的联系。典型的图数据库系统是Neo4j(https://neo4j.com/)或ArangoDB(https://www.arangodb.com/)。

3.1.3 小型数据库

与其他的类型相比,这类数据库有些特别。小型数据库系统并非以一个独立的客户端-服务器结构的形式存在,而是被嵌入应用程序的代码中,直接从硬盘驱动器中读取数据。它们通常用于仅需运行单一进程的简单应用程序,且希望以结构化的方式保存信息。

一种简单有效的实现方法是,将信息作为JSON对象保存到文件中,并在需要时还原其内容,例如,智能手机App的客户端配置信息。当App从内存中启动时,加载其配置文件,如果配置发生变化,则将其保存。

举个例子,用Python代码实现如下:

对于少量的数据,这种方式也许可以解决问题,但其局限性是查询不便。最有效的替代方案是采用SQLite,它是一个成熟的SQL数据库,但被嵌入应用系统中,不需要从外部进行调用。SQLite的数据存储在二进制文件中。

SQLite非常受欢迎,乃至很多标准库中都包含对它的支持,且无须外部模块,例如,Python的标准库就是如此:

这个模块遵循DB-API 2.0规范,它是用于数据库连接的Python标准。其目的是实现对不同数据库后端访问的标准化。这会使创建一个可以访问多个SQL数据库的高级模块变得很容易,并能以最小的改动将其用于其他应用程序。

可以在PEP-249中查看完整的DB-API 2.0规范:https://www.python.org/dev/peps/pep-0249/。

SQLite数据库实现了大部分标准的SQL功能。 n6RGCApLLwizRK0fntjgex+PI6V5NNjIc6YRtAgWcOrCN1RAgCCnP3TsomxIfZh/

点击中间区域
呼出菜单
上一章
目录
下一章
×