From bf82fb320991002630e434d4b555acc0e317288f Mon Sep 17 00:00:00 2001 From: Gang Yin <1246410+yingang@users.noreply.github.com> Date: Wed, 19 Jan 2022 19:42:30 +0800 Subject: [PATCH] update text spacing with pandu and some manual adjustments --- README.md | 14 +- ch10.md | 4 +- ch2.md | 429 +++++++++++++++++++++++---------------------- colophon.md | 14 +- glossary.md | 2 +- part-i.md | 14 +- part-ii.md | 44 ++--- part-iii.md | 12 +- preface.md | 12 +- zh-tw/README.md | 8 +- zh-tw/ch10.md | 4 +- zh-tw/ch2.md | 430 +++++++++++++++++++++++----------------------- zh-tw/colophon.md | 14 +- zh-tw/glossary.md | 2 +- zh-tw/part-i.md | 10 +- zh-tw/part-ii.md | 34 ++-- zh-tw/part-iii.md | 12 +- zh-tw/preface.md | 12 +- 18 files changed, 535 insertions(+), 536 deletions(-) diff --git a/README.md b/README.md index b785da53..6341825f 100644 --- a/README.md +++ b/README.md @@ -1,4 +1,4 @@ -# 设计数据密集型应用 - 中文翻译 +# 设计数据密集型应用 - 中文翻译 - 作者: [Martin Kleppmann](https://martin.kleppmann.com) - 原名:[《Designing Data-Intensive Applications》](http://shop.oreilly.com/product/0636920032175.do) @@ -8,8 +8,8 @@ > 使用 [Typora](https://www.typora.io)、[Gitbook](https://vonng.gitbook.io/vonng/) 或 [Github Pages](https://vonng.github.io/ddia) 以获取最佳阅读体验。 -> -> 本地:你可在项目根目录中执行`make`,并通过浏览器阅读([在线预览](http://ddia.vonng.com/#/))。 +> +> 本地:你可在项目根目录中执行 `make`,并通过浏览器阅读([在线预览](http://ddia.vonng.com/#/))。 ## 译序 @@ -25,7 +25,7 @@ 本书为数据系统的设计、实现、与评价提供了很好的概念框架。读完并理解本书内容后,读者可以轻松看破大多数的技术忽悠,与技术砖家撕起来虎虎生风🤣。 -这是2017年译者读过最好的一本技术类书籍,这么好的书没有中文翻译,实在是遗憾。某不才,愿为先进技术文化的传播贡献一份力量。既可以深入学习有趣的技术主题,又可以锻炼中英文语言文字功底,何乐而不为? +这是 2017 年译者读过最好的一本技术类书籍,这么好的书没有中文翻译,实在是遗憾。某不才,愿为先进技术文化的传播贡献一份力量。既可以深入学习有趣的技术主题,又可以锻炼中英文语言文字功底,何乐而不为? ## 前言 @@ -34,9 +34,9 @@ --------- -> 计算是一种流行文化,流行文化鄙视历史。 流行文化关乎个体身份和参与感,但与合作无关。流行文化活在当下,也与过去和未来无关。 我认为大部分(为了钱)编写代码的人就是这样的, 他们不知道自己的文化来自哪里。 +> 计算是一种流行文化,流行文化鄙视历史。 流行文化关乎个体身份和参与感,但与合作无关。流行文化活在当下,也与过去和未来无关。 我认为大部分(为了钱)编写代码的人就是这样的, 他们不知道自己的文化来自哪里。 > -> ——阿兰·凯接受Dobb博士的杂志采访时(2012年) +> —— 阿兰・凯接受 Dobb 博士的杂志采访时(2012 年) ## 目录 @@ -127,7 +127,7 @@ 从原作者处得知,已经有简体中文的翻译计划,将于2018年末完成。[购买地址](https://search.jd.com/Search?keyword=设计数据密集型应用) -译者纯粹出于**学习目的**与**个人兴趣**翻译本书,不追求任何经济利益。 +译者纯粹出于 **学习目的** 与 **个人兴趣** 翻译本书,不追求任何经济利益。 译者保留对此版本译文的署名权,其他权利以原作者和出版社的主张为准。 diff --git a/ch10.md b/ch10.md index 846428a1..e2d50e2b 100644 --- a/ch10.md +++ b/ch10.md @@ -222,7 +222,7 @@ MapReduce是一个编程框架,你可以使用它编写代码来处理HDFS等 这四个步骤可以作为一个MapReduce作业执行。步骤2(Map)和4(Reduce)是你编写自定义数据处理代码的地方。步骤1(将文件分解成记录)由输入格式解析器处理。步骤3中的排序步骤隐含在MapReduce中 —— 你不必编写它,因为Mapper的输出始终在送往Reducer之前进行排序。 -要创建MapReduce作业,你需要实现两个回调函数,Mapper和Reducer,其行为如下(请参阅“[MapReduce查询](ch2.md#MapReduce查询)”): +要创建MapReduce作业,你需要实现两个回调函数,Mapper和Reducer,其行为如下(请参阅“[MapReduce 查询](ch2.md#MapReduce查询)”): * Mapper @@ -238,7 +238,7 @@ MapReduce是一个编程框架,你可以使用它编写代码来处理HDFS等 MapReduce与Unix命令管道的主要区别在于,MapReduce可以在多台机器上并行执行计算,而无需编写代码来显式处理并行问题。Mapper和Reducer一次只能处理一条记录;它们不需要知道它们的输入来自哪里,或者输出去往什么地方,所以框架可以处理在机器之间移动数据的复杂性。 -在分布式计算中可以使用标准的Unix工具作为Mapper和Reducer【25】,但更常见的是,它们被实现为传统编程语言的函数。在Hadoop MapReduce中,Mapper和Reducer都是实现特定接口的Java类。在MongoDB和CouchDB中,Mapper和Reducer都是JavaScript函数(请参阅“[MapReduce查询](ch2.md#MapReduce查询)”)。 +在分布式计算中可以使用标准的Unix工具作为Mapper和Reducer【25】,但更常见的是,它们被实现为传统编程语言的函数。在Hadoop MapReduce中,Mapper和Reducer都是实现特定接口的Java类。在MongoDB和CouchDB中,Mapper和Reducer都是JavaScript函数(请参阅“[MapReduce 查询](ch2.md#MapReduce查询)”)。 [图10-1](img/fig10-1.png)显示了Hadoop MapReduce作业中的数据流。其并行化基于分区(请参阅[第六章](ch6.md)):作业的输入通常是HDFS中的一个目录,输入目录中的每个文件或文件块都被认为是一个单独的分区,可以单独处理map任务([图10-1](img/fig10-1.png)中的m1,m2和m3标记)。 diff --git a/ch2.md b/ch2.md index bb69e311..3a587a87 100644 --- a/ch2.md +++ b/ch2.md @@ -4,79 +4,79 @@ > 语言的边界就是思想的边界。 > -> —— 路德维奇·维特根斯坦,《逻辑哲学》(1922) +> —— 路德维奇・维特根斯坦,《逻辑哲学》(1922) > ------------------- [TOC] -数据模型可能是软件开发中最重要的部分了,因为它们的影响如此深远:不仅仅影响着软件的编写方式,而且影响着我们的**解题思路**。 +数据模型可能是软件开发中最重要的部分了,因为它们的影响如此深远:不仅仅影响着软件的编写方式,而且影响着我们的 **解题思路**。 -多数应用使用层层叠加的数据模型构建。对于每层数据模型的关键问题是:它是如何用低一层数据模型来**表示**的?例如: +多数应用使用层层叠加的数据模型构建。对于每层数据模型的关键问题是:它是如何用低一层数据模型来 **表示** 的?例如: -1. 作为一名应用开发人员,你观察现实世界(里面有人员、组织、货物、行为、资金流向、传感器等),并采用对象或数据结构,以及操控那些数据结构的API来进行建模。那些结构通常是特定于应用程序的。 -2. 当要存储那些数据结构时,你可以利用通用数据模型来表示它们,如JSON或XML文档、关系数据库中的表或图模型。 -3. 数据库软件的工程师选定如何以内存、磁盘或网络上的字节来表示JSON/XML/关系/图数据。这类表示形式使数据有可能以各种方式来查询,搜索,操纵和处理。 +1. 作为一名应用开发人员,你观察现实世界(里面有人员、组织、货物、行为、资金流向、传感器等),并采用对象或数据结构,以及操控那些数据结构的 API 来进行建模。那些结构通常是特定于应用程序的。 +2. 当要存储那些数据结构时,你可以利用通用数据模型来表示它们,如 JSON 或 XML 文档、关系数据库中的表或图模型。 +3. 数据库软件的工程师选定如何以内存、磁盘或网络上的字节来表示 JSON / XML/ 关系 / 图数据。这类表示形式使数据有可能以各种方式来查询,搜索,操纵和处理。 4. 在更低的层次上,硬件工程师已经想出了使用电流、光脉冲、磁场或者其他东西来表示字节的方法。 -一个复杂的应用程序可能会有更多的中间层次,比如基于API的API,不过基本思想仍然是一样的:每个层都通过提供一个明确的数据模型来隐藏更低层次中的复杂性。这些抽象允许不同的人群有效地协作(例如数据库厂商的工程师和使用数据库的应用程序开发人员)。 +一个复杂的应用程序可能会有更多的中间层次,比如基于 API 的 API,不过基本思想仍然是一样的:每个层都通过提供一个明确的数据模型来隐藏更低层次中的复杂性。这些抽象允许不同的人群有效地协作(例如数据库厂商的工程师和使用数据库的应用程序开发人员)。 数据模型种类繁多,每个数据模型都带有如何使用的设想。有些用法很容易,有些则不支持如此;有些操作运行很快,有些则表现很差;有些数据转换非常自然,有些则很麻烦。 掌握一个数据模型需要花费很多精力(想想关系数据建模有多少本书)。即便只使用一个数据模型,不用操心其内部工作机制,构建软件也是非常困难的。然而,因为数据模型对上层软件的功能(能做什么,不能做什么)有着至深的影响,所以选择一个适合的数据模型是非常重要的。 -在本章中,我们将研究一系列用于数据存储和查询的通用数据模型(前面列表中的第2点)。特别地,我们将比较关系模型,文档模型和少量基于图形的数据模型。我们还将查看各种查询语言并比较它们的用例。在[第三章](ch3.md)中,我们将讨论存储引擎是如何工作的。也就是说,这些数据模型实际上是如何实现的(列表中的第3点)。 +在本章中,我们将研究一系列用于数据存储和查询的通用数据模型(前面列表中的第 2 点)。特别地,我们将比较关系模型,文档模型和少量基于图形的数据模型。我们还将查看各种查询语言并比较它们的用例。在 [第三章](ch3.md) 中,我们将讨论存储引擎是如何工作的。也就是说,这些数据模型实际上是如何实现的(列表中的第 3 点)。 ## 关系模型与文档模型 -现在最著名的数据模型可能是SQL。它基于Edgar Codd在1970年提出的关系模型【1】:数据被组织成**关系**(SQL中称作**表**),其中每个关系是**元组**(SQL中称作**行**)的无序集合。 +现在最著名的数据模型可能是 SQL。它基于 Edgar Codd 在 1970 年提出的关系模型【1】:数据被组织成 **关系**(SQL 中称作 **表**),其中每个关系是 **元组**(SQL 中称作 **行**) 的无序集合。 -关系模型曾是一个理论性的提议,当时很多人都怀疑是否能够有效实现它。然而到了20世纪80年代中期,关系数据库管理系统(RDBMSes)和SQL已成为大多数人们存储和查询某些常规结构的数据的首选工具。关系数据库已经持续称霸了大约25~30年——这对计算机史来说是极其漫长的时间。 +关系模型曾是一个理论性的提议,当时很多人都怀疑是否能够有效实现它。然而到了 20 世纪 80 年代中期,关系数据库管理系统(RDBMSes)和 SQL 已成为大多数人们存储和查询某些常规结构的数据的首选工具。关系数据库已经持续称霸了大约 25~30 年 —— 这对计算机史来说是极其漫长的时间。 -关系数据库起源于商业数据处理,在20世纪60年代和70年代用大型计算机来执行。从今天的角度来看,那些用例显得很平常:典型的**事务处理**(将销售或银行交易,航空公司预订,库存管理信息记录在库)和**批处理**(客户发票,工资单,报告)。 +关系数据库起源于商业数据处理,在 20 世纪 60 年代和 70 年代用大型计算机来执行。从今天的角度来看,那些用例显得很平常:典型的 **事务处理**(将销售或银行交易,航空公司预订,库存管理信息记录在库)和 **批处理**(客户发票,工资单,报告)。 当时的其他数据库迫使应用程序开发人员必须考虑数据库内部的数据表示形式。关系模型致力于将上述实现细节隐藏在更简洁的接口之后。 -多年来,在数据存储和查询方面存在着许多相互竞争的方法。在20世纪70年代和80年代初,网状模型(network model)和层次模型(hierarchical model)曾是主要的选择,但关系模型(relational model)随后占据了主导地位。对象数据库在20世纪80年代末和90年代初来了又去。XML数据库在二十一世纪初出现,但只有小众采用过。关系模型的每个竞争者都在其时代产生了大量的炒作,但从来没有持续【2】。 +多年来,在数据存储和查询方面存在着许多相互竞争的方法。在 20 世纪 70 年代和 80 年代初,网状模型(network model)和层次模型(hierarchical model)曾是主要的选择,但关系模型(relational model)随后占据了主导地位。对象数据库在 20 世纪 80 年代末和 90 年代初来了又去。XML 数据库在二十一世纪初出现,但只有小众采用过。关系模型的每个竞争者都在其时代产生了大量的炒作,但从来没有持续【2】。 随着电脑越来越强大和互联,它们开始用于日益多样化的目的。关系数据库非常成功地被推广到业务数据处理的原始范围之外更为广泛的用例上。你今天在网上看到的大部分内容依旧是由关系数据库来提供支持,无论是在线发布,讨论,社交网络,电子商务,游戏,软件即服务生产力应用程序等等内容。 -### NoSQL的诞生 +### NoSQL 的诞生 -现在 - 2010年代,NoSQL开始了最新一轮尝试,试图推翻关系模型的统治地位。“NoSQL”这个名字让人遗憾,因为实际上它并没有涉及到任何特定的技术。最初它只是作为一个醒目的Twitter标签,用在2009年一个关于分布式,非关系数据库上的开源聚会上。无论如何,这个术语触动了某些神经,并迅速在网络创业社区内外传播开来。好些有趣的数据库系统现在都与 *#NoSQL* 标签相关联,并且NoSQL被追溯性地重新解释为**不仅是SQL(Not Only SQL)** 【4】。 +现在 - 2010 年代,NoSQL 开始了最新一轮尝试,试图推翻关系模型的统治地位。“NoSQL” 这个名字让人遗憾,因为实际上它并没有涉及到任何特定的技术。最初它只是作为一个醒目的 Twitter 标签,用在 2009 年一个关于分布式,非关系数据库上的开源聚会上。无论如何,这个术语触动了某些神经,并迅速在网络创业社区内外传播开来。好些有趣的数据库系统现在都与 *#NoSQL* 标签相关联,并且 NoSQL 被追溯性地重新解释为 **不仅是 SQL(Not Only SQL)** 【4】。 -采用NoSQL数据库的背后有几个驱动因素,其中包括: +采用 NoSQL 数据库的背后有几个驱动因素,其中包括: * 需要比关系数据库更好的可伸缩性,包括非常大的数据集或非常高的写入吞吐量 * 相比商业数据库产品,免费和开源软件更受偏爱 * 关系模型不能很好地支持一些特殊的查询操作 * 受挫于关系模型的限制性,渴望一种更具多动态性与表现力的数据模型【5】 -不同的应用程序有不同的需求,一个用例的最佳技术选择可能不同于另一个用例的最佳技术选择。因此,在可预见的未来,关系数据库似乎可能会继续与各种非关系数据库一起使用 - 这种想法有时也被称为**混合持久化(polyglot persistence)**。 +不同的应用程序有不同的需求,一个用例的最佳技术选择可能不同于另一个用例的最佳技术选择。因此,在可预见的未来,关系数据库似乎可能会继续与各种非关系数据库一起使用 - 这种想法有时也被称为 **混合持久化(polyglot persistence)**。 ### 对象关系不匹配 -目前大多数应用程序开发都使用面向对象的编程语言来开发,这导致了对SQL数据模型的普遍批评:如果数据存储在关系表中,那么需要一个笨拙的转换层,处于应用程序代码中的对象和表,行,列的数据库模型之间。模型之间的不连贯有时被称为**阻抗不匹配(impedance mismatch)**[^i]。 +目前大多数应用程序开发都使用面向对象的编程语言来开发,这导致了对 SQL 数据模型的普遍批评:如果数据存储在关系表中,那么需要一个笨拙的转换层,处于应用程序代码中的对象和表,行,列的数据库模型之间。模型之间的不连贯有时被称为 **阻抗不匹配(impedance mismatch)**[^i]。 [^i]: 一个从电子学借用的术语。每个电路的输入和输出都有一定的阻抗(交流电阻)。当你将一个电路的输出连接到另一个电路的输入时,如果两个电路的输出和输入阻抗匹配,则连接上的功率传输将被最大化。阻抗不匹配会导致信号反射及其他问题。 -像ActiveRecord和Hibernate这样的 **对象关系映射(ORM object-relational mapping)** 框架可以减少这个转换层所需的样板代码的数量,但是它们不能完全隐藏这两个模型之间的差异。 +像 ActiveRecord 和 Hibernate 这样的 **对象关系映射(ORM object-relational mapping)** 框架可以减少这个转换层所需的样板代码的数量,但是它们不能完全隐藏这两个模型之间的差异。 ![](img/fig2-1.png) -**图2-1 使用关系型模式来表示领英简介** +**图 2-1 使用关系型模式来表示领英简介** -例如,[图2-1](img/fig2-1.png)展示了如何在关系模式中表示简历(一个LinkedIn简介)。整个简介可以通过一个唯一的标识符`user_id`来标识。像`first_name`和`last_name`这样的字段每个用户只出现一次,所以可以在User表上将其建模为列。但是,大多数人在职业生涯中拥有多于一份的工作,人们可能有不同样的教育阶段和任意数量的联系信息。从用户到这些项目之间存在一对多的关系,可以用多种方式来表示: +例如,[图 2-1](img/fig2-1.png) 展示了如何在关系模式中表示简历(一个 LinkedIn 简介)。整个简介可以通过一个唯一的标识符 `user_id` 来标识。像 `first_name` 和 `last_name` 这样的字段每个用户只出现一次,所以可以在 User 表上将其建模为列。但是,大多数人在职业生涯中拥有多于一份的工作,人们可能有不同样的教育阶段和任意数量的联系信息。从用户到这些项目之间存在一对多的关系,可以用多种方式来表示: -* 传统SQL模型(SQL:1999之前)中,最常见的规范化表示形式是将职位,教育和联系信息放在单独的表中,对User表提供外键引用,如[图2-1](img/fig2-1.png)所示。 -* 后续的SQL标准增加了对结构化数据类型和XML数据的支持;这允许将多值数据存储在单行内,并支持在这些文档内查询和索引。这些功能在Oracle,IBM DB2,MS SQL Server和PostgreSQL中都有不同程度的支持【6,7】。JSON数据类型也得到多个数据库的支持,包括IBM DB2,MySQL和PostgreSQL 【8】。 -* 第三种选择是将职业,教育和联系信息编码为JSON或XML文档,将其存储在数据库的文本列中,并让应用程序解析其结构和内容。这种配置下,通常不能使用数据库来查询该编码列中的值。 +* 传统 SQL 模型(SQL:1999 之前)中,最常见的规范化表示形式是将职位,教育和联系信息放在单独的表中,对 User 表提供外键引用,如 [图 2-1](img/fig2-1.png) 所示。 +* 后续的 SQL 标准增加了对结构化数据类型和 XML 数据的支持;这允许将多值数据存储在单行内,并支持在这些文档内查询和索引。这些功能在 Oracle,IBM DB2,MS SQL Server 和 PostgreSQL 中都有不同程度的支持【6,7】。JSON 数据类型也得到多个数据库的支持,包括 IBM DB2,MySQL 和 PostgreSQL 【8】。 +* 第三种选择是将职业,教育和联系信息编码为 JSON 或 XML 文档,将其存储在数据库的文本列中,并让应用程序解析其结构和内容。这种配置下,通常不能使用数据库来查询该编码列中的值。 -对于一个像简历这样自包含文档的数据结构而言,JSON表示是非常合适的:请参阅[例2-1]()。JSON比XML更简单。面向文档的数据库(如MongoDB 【9】,RethinkDB 【10】,CouchDB 【11】和Espresso【12】)支持这种数据模型。 +对于一个像简历这样自包含文档的数据结构而言,JSON 表示是非常合适的:请参阅 [例 2-1]()。JSON 比 XML 更简单。面向文档的数据库(如 MongoDB 【9】,RethinkDB 【10】,CouchDB 【11】和 Espresso【12】)支持这种数据模型。 -**例2-1. 用JSON文档表示一个LinkedIn简介** +**例 2-1. 用 JSON 文档表示一个 LinkedIn 简介** ```json { @@ -116,39 +116,39 @@ } ``` -有一些开发人员认为JSON模型减少了应用程序代码和存储层之间的阻抗不匹配。不过,正如我们将在[第四章](ch4.md)中看到的那样,JSON作为数据编码格式也存在问题。缺乏一个模式往往被认为是一个优势;我们将在“[文档模型中的模式灵活性](#文档模型中的模式灵活性)”中讨论这个问题。 +有一些开发人员认为 JSON 模型减少了应用程序代码和存储层之间的阻抗不匹配。不过,正如我们将在 [第四章](ch4.md) 中看到的那样,JSON 作为数据编码格式也存在问题。缺乏一个模式往往被认为是一个优势;我们将在 “[文档模型中的模式灵活性](#文档模型中的模式灵活性)” 中讨论这个问题。 -JSON表示比[图2-1](img/fig2-1.png)中的多表模式具有更好的**局部性(locality)**。如果在前面的关系型示例中获取简介,那需要执行多个查询(通过`user_id`查询每个表),或者在User表与其下属表之间混乱地执行多路连接。而在JSON表示中,所有相关信息都在同一个地方,一个查询就足够了。 +JSON 表示比 [图 2-1](img/fig2-1.png) 中的多表模式具有更好的 **局部性(locality)**。如果在前面的关系型示例中获取简介,那需要执行多个查询(通过 `user_id` 查询每个表),或者在 User 表与其下属表之间混乱地执行多路连接。而在 JSON 表示中,所有相关信息都在同一个地方,一个查询就足够了。 -从用户简介文件到用户职位,教育历史和联系信息,这种一对多关系隐含了数据中的一个树状结构,而JSON表示使得这个树状结构变得明确(见[图2-2](img/fig2-2.png))。 +从用户简介文件到用户职位,教育历史和联系信息,这种一对多关系隐含了数据中的一个树状结构,而 JSON 表示使得这个树状结构变得明确(见 [图 2-2](img/fig2-2.png))。 ![](img/fig2-2.png) -**图2-2 一对多关系构建了一个树结构** +**图 2-2 一对多关系构建了一个树结构** ### 多对一和多对多的关系 -在上一节的[例2-1]()中,`region_id`和`industry_id`是以ID,而不是纯字符串“Greater Seattle Area”和“Philanthropy”的形式给出的。为什么? +在上一节的 [例 2-1]() 中,`region_id` 和 `industry_id` 是以 ID,而不是纯字符串 “Greater Seattle Area” 和 “Philanthropy” 的形式给出的。为什么? 如果用户界面用一个自由文本字段来输入区域和行业,那么将他们存储为纯文本字符串是合理的。另一方式是给出地理区域和行业的标准化的列表,并让用户从下拉列表或自动填充器中进行选择,其优势如下: * 各个简介之间样式和拼写统一 * 避免歧义(例如,如果有几个同名的城市) -* 易于更新——名称只存储在一个地方,如果需要更改(例如,由于政治事件而改变城市名称),很容易进行全面更新。 -* 本地化支持——当网站翻译成其他语言时,标准化的列表可以被本地化,使得地区和行业可以使用用户的语言来显示 -* 更好的搜索——例如,搜索华盛顿州的慈善家就会匹配这份简介,因为地区列表可以编码记录西雅图在华盛顿这一事实(从“Greater Seattle Area”这个字符串中看不出来) +* 易于更新 —— 名称只存储在一个地方,如果需要更改(例如,由于政治事件而改变城市名称),很容易进行全面更新。 +* 本地化支持 —— 当网站翻译成其他语言时,标准化的列表可以被本地化,使得地区和行业可以使用用户的语言来显示 +* 更好的搜索 —— 例如,搜索华盛顿州的慈善家就会匹配这份简介,因为地区列表可以编码记录西雅图在华盛顿这一事实(从 “Greater Seattle Area” 这个字符串中看不出来) -存储ID还是文本字符串,这是个 **副本(duplication)** 问题。当使用ID时,对人类有意义的信息(比如单词:Philanthropy)只存储在一处,所有引用它的地方使用ID(ID只在数据库中有意义)。当直接存储文本时,对人类有意义的信息会复制在每处使用记录中。 +存储 ID 还是文本字符串,这是个 **副本(duplication)** 问题。当使用 ID 时,对人类有意义的信息(比如单词:Philanthropy)只存储在一处,所有引用它的地方使用 ID(ID 只在数据库中有意义)。当直接存储文本时,对人类有意义的信息会复制在每处使用记录中。 -使用ID的好处是,ID对人类没有任何意义,因而永远不需要改变:ID可以保持不变,即使它标识的信息发生变化。任何对人类有意义的东西都可能需要在将来某个时候改变——如果这些信息被复制,所有的冗余副本都需要更新。这会导致写入开销,也存在不一致的风险(一些副本被更新了,还有些副本没有被更新)。去除此类重复是数据库 **规范化(normalization)** 的关键思想。[^ii] +使用 ID 的好处是,ID 对人类没有任何意义,因而永远不需要改变:ID 可以保持不变,即使它标识的信息发生变化。任何对人类有意义的东西都可能需要在将来某个时候改变 —— 如果这些信息被复制,所有的冗余副本都需要更新。这会导致写入开销,也存在不一致的风险(一些副本被更新了,还有些副本没有被更新)。去除此类重复是数据库 **规范化(normalization)** 的关键思想。[^ii] -[^ii]: 关于关系模型的文献区分了几种不同的规范形式,但这些区别几乎没有实际意义。一个经验法则是,如果重复存储了可以存储在一个地方的值,则模式就不是**规范化(normalized)** 的。 +[^ii]: 关于关系模型的文献区分了几种不同的规范形式,但这些区别几乎没有实际意义。一个经验法则是,如果重复存储了可以存储在一个地方的值,则模式就不是 **规范化(normalized)** 的。 -> 数据库管理员和开发人员喜欢争论规范化和非规范化,让我们暂时保留判断吧。在本书的[第三部分](part-iii.md),我们将回到这个话题,探讨系统的方法用以处理缓存,非规范化和衍生数据。 +> 数据库管理员和开发人员喜欢争论规范化和非规范化,让我们暂时保留判断吧。在本书的 [第三部分](part-iii.md),我们将回到这个话题,探讨系统的方法用以处理缓存,非规范化和衍生数据。 -不幸的是,对这些数据进行规范化需要多对一的关系(许多人生活在一个特定的地区,许多人在一个特定的行业工作),这与文档模型不太吻合。在关系数据库中,通过ID来引用其他表中的行是正常的,因为连接很容易。在文档数据库中,一对多树结构没有必要用连接,对连接的支持通常很弱[^iii]。 +不幸的是,对这些数据进行规范化需要多对一的关系(许多人生活在一个特定的地区,许多人在一个特定的行业工作),这与文档模型不太吻合。在关系数据库中,通过 ID 来引用其他表中的行是正常的,因为连接很容易。在文档数据库中,一对多树结构没有必要用连接,对连接的支持通常很弱 [^iii]。 -[^iii]: 在撰写本文时,RethinkDB支持连接,MongoDB不支持连接,而CouchDB只支持预先声明的视图。 +[^iii]: 在撰写本文时,RethinkDB 支持连接,MongoDB 不支持连接,而 CouchDB 只支持预先声明的视图。 如果数据库本身不支持连接,则必须在应用程序代码中通过对数据库进行多个查询来模拟连接。(在这种情况中,地区和行业的列表可能很小,改动很少,应用程序可以简单地将其保存在内存中。不过,执行连接的工作从数据库被转移到应用程序代码上。 @@ -156,7 +156,7 @@ JSON表示比[图2-1](img/fig2-1.png)中的多表模式具有更好的**局部 * 组织和学校作为实体 - 在前面的描述中,`organization`(用户工作的公司)和`school_name`(他们学习的地方)只是字符串。也许他们应该是对实体的引用呢?然后,每个组织、学校或大学都可以拥有自己的网页(标识,新闻提要等)。每个简历可以链接到它所提到的组织和学校,并且包括他们的图标和其他信息(请参阅[图2-3](img/fig2-3.png),来自LinkedIn的一个例子)。 + 在前面的描述中,`organization`(用户工作的公司)和 `school_name`(他们学习的地方)只是字符串。也许他们应该是对实体的引用呢?然后,每个组织、学校或大学都可以拥有自己的网页(标识,新闻提要等)。每个简历可以链接到它所提到的组织和学校,并且包括他们的图标和其他信息(请参阅 [图 2-3](img/fig2-3.png),来自 LinkedIn 的一个例子)。 * 推荐 @@ -164,83 +164,83 @@ JSON表示比[图2-1](img/fig2-1.png)中的多表模式具有更好的**局部 ![](img/fig2-3.png) -**图2-3 公司名不仅是字符串,还是一个指向公司实体的链接(LinkedIn截图)** +**图 2-3 公司名不仅是字符串,还是一个指向公司实体的链接(LinkedIn 截图)** -[图2-4](img/fig2-4.png)阐明了这些新功能需要如何使用多对多关系。每个虚线矩形内的数据可以分组成一个文档,但是对单位,学校和其他用户的引用需要表示成引用,并且在查询时需要连接。 +[图 2-4](img/fig2-4.png) 阐明了这些新功能需要如何使用多对多关系。每个虚线矩形内的数据可以分组成一个文档,但是对单位,学校和其他用户的引用需要表示成引用,并且在查询时需要连接。 ![](img/fig2-4.png) -**图2-4 使用多对多关系扩展简历** +**图 2-4 使用多对多关系扩展简历** ### 文档数据库是否在重蹈覆辙? -在多对多的关系和连接已常规用在关系数据库时,文档数据库和NoSQL重启了辩论:如何以最佳方式在数据库中表示多对多关系。那场辩论可比NoSQL古老得多,事实上,最早可以追溯到计算机化数据库系统。 +在多对多的关系和连接已常规用在关系数据库时,文档数据库和 NoSQL 重启了辩论:如何以最佳方式在数据库中表示多对多关系。那场辩论可比 NoSQL 古老得多,事实上,最早可以追溯到计算机化数据库系统。 -20世纪70年代最受欢迎的业务数据处理数据库是IBM的信息管理系统(IMS),最初是为了阿波罗太空计划的库存管理而开发的,并于1968年有了首次商业发布【13】。目前它仍在使用和维护,运行在IBM大型机的OS/390上【14】。 +20 世纪 70 年代最受欢迎的业务数据处理数据库是 IBM 的信息管理系统(IMS),最初是为了阿波罗太空计划的库存管理而开发的,并于 1968 年有了首次商业发布【13】。目前它仍在使用和维护,运行在 IBM 大型机的 OS/390 上【14】。 -IMS的设计中使用了一个相当简单的数据模型,称为**层次模型(hierarchical model)**,它与文档数据库使用的JSON模型有一些惊人的相似之处【2】。它将所有数据表示为嵌套在记录中的记录树,这很像[图2-2](img/fig2-2.png)的JSON结构。 +IMS 的设计中使用了一个相当简单的数据模型,称为 **层次模型(hierarchical model)**,它与文档数据库使用的 JSON 模型有一些惊人的相似之处【2】。它将所有数据表示为嵌套在记录中的记录树,这很像 [图 2-2](img/fig2-2.png) 的 JSON 结构。 -同文档数据库一样,IMS能良好处理一对多的关系,但是很难应对多对多的关系,并且不支持连接。开发人员必须决定是否复制(非规范化)数据或手动解决从一个记录到另一个记录的引用。这些二十世纪六七十年代的问题与现在开发人员遇到的文档数据库问题非常相似【15】。 +同文档数据库一样,IMS 能良好处理一对多的关系,但是很难应对多对多的关系,并且不支持连接。开发人员必须决定是否复制(非规范化)数据或手动解决从一个记录到另一个记录的引用。这些二十世纪六七十年代的问题与现在开发人员遇到的文档数据库问题非常相似【15】。 -那时人们提出了各种不同的解决方案来解决层次模型的局限性。其中最突出的两个是**关系模型**(relational model,它变成了SQL,并统治了世界)和**网状模型**(network model,最初很受关注,但最终变得冷门)。这两个阵营之间的“大辩论”在70年代持续了很久时间【2】。 +那时人们提出了各种不同的解决方案来解决层次模型的局限性。其中最突出的两个是 **关系模型**(relational model,它变成了 SQL,并统治了世界)和 **网状模型**(network model,最初很受关注,但最终变得冷门)。这两个阵营之间的 “大辩论” 在 70 年代持续了很久时间【2】。 那两个模式解决的问题与当前的问题相关,因此值得简要回顾一下那场辩论。 #### 网状模型 -网状模型由一个称为数据系统语言会议(CODASYL)的委员会进行了标准化,并被数个不同的数据库厂商实现;它也被称为CODASYL模型【16】。 +网状模型由一个称为数据系统语言会议(CODASYL)的委员会进行了标准化,并被数个不同的数据库厂商实现;它也被称为 CODASYL 模型【16】。 -CODASYL模型是层次模型的推广。在层次模型的树结构中,每条记录只有一个父节点;在网络模式中,每条记录可能有多个父节点。例如,“Greater Seattle Area”地区可能是一条记录,每个居住在该地区的用户都可以与之相关联。这允许对多对一和多对多的关系进行建模。 +CODASYL 模型是层次模型的推广。在层次模型的树结构中,每条记录只有一个父节点;在网络模式中,每条记录可能有多个父节点。例如,“Greater Seattle Area” 地区可能是一条记录,每个居住在该地区的用户都可以与之相关联。这允许对多对一和多对多的关系进行建模。 -网状模型中记录之间的链接不是外键,而更像编程语言中的指针(同时仍然存储在磁盘上)。访问记录的唯一方法是跟随从根记录起沿这些链路所形成的路径。这被称为**访问路径(access path)**。 +网状模型中记录之间的链接不是外键,而更像编程语言中的指针(同时仍然存储在磁盘上)。访问记录的唯一方法是跟随从根记录起沿这些链路所形成的路径。这被称为 **访问路径(access path)**。 最简单的情况下,访问路径类似遍历链表:从列表头开始,每次查看一条记录,直到找到所需的记录。但在多对多关系的情况中,数条不同的路径可以到达相同的记录,网状模型的程序员必须跟踪这些不同的访问路径。 -CODASYL中的查询是通过利用遍历记录列和跟随访问路径表在数据库中移动游标来执行的。如果记录有多个父结点(即多个来自其他记录的传入指针),则应用程序代码必须跟踪所有的各种关系。甚至CODASYL委员会成员也承认,这就像在n维数据空间中进行导航【17】。 +CODASYL 中的查询是通过利用遍历记录列和跟随访问路径表在数据库中移动游标来执行的。如果记录有多个父结点(即多个来自其他记录的传入指针),则应用程序代码必须跟踪所有的各种关系。甚至 CODASYL 委员会成员也承认,这就像在 n 维数据空间中进行导航【17】。 -尽管手动选择访问路径够能最有效地利用20世纪70年代非常有限的硬件功能(如磁带驱动器,其搜索速度非常慢),但这使得查询和更新数据库的代码变得复杂不灵活。无论是分层还是网状模型,如果你没有所需数据的路径,就会陷入困境。你可以改变访问路径,但是必须浏览大量手写数据库查询代码,并重写来处理新的访问路径。更改应用程序的数据模型是很难的。 +尽管手动选择访问路径够能最有效地利用 20 世纪 70 年代非常有限的硬件功能(如磁带驱动器,其搜索速度非常慢),但这使得查询和更新数据库的代码变得复杂不灵活。无论是分层还是网状模型,如果你没有所需数据的路径,就会陷入困境。你可以改变访问路径,但是必须浏览大量手写数据库查询代码,并重写来处理新的访问路径。更改应用程序的数据模型是很难的。 #### 关系模型 -相比之下,关系模型做的就是将所有的数据放在光天化日之下:一个 **关系(表)** 只是一个 **元组(行)** 的集合,仅此而已。如果你想读取数据,它没有迷宫似的嵌套结构,也没有复杂的访问路径。你可以选中符合任意条件的行,读取表中的任何或所有行。你可以通过指定某些列作为匹配关键字来读取特定行。你可以在任何表中插入一个新的行,而不必担心与其他表的外键关系[^iv]。 +相比之下,关系模型做的就是将所有的数据放在光天化日之下:一个 **关系(表)** 只是一个 **元组(行)** 的集合,仅此而已。如果你想读取数据,它没有迷宫似的嵌套结构,也没有复杂的访问路径。你可以选中符合任意条件的行,读取表中的任何或所有行。你可以通过指定某些列作为匹配关键字来读取特定行。你可以在任何表中插入一个新的行,而不必担心与其他表的外键关系 [^iv]。 -[^iv]: 外键约束允许对修改进行限制,但对于关系模型这并不是必选项。即使有约束,外键连接在查询时执行,而在CODASYL中,连接在插入时高效完成。 +[^iv]: 外键约束允许对修改进行限制,但对于关系模型这并不是必选项。即使有约束,外键连接在查询时执行,而在 CODASYL 中,连接在插入时高效完成。 -在关系数据库中,查询优化器自动决定查询的哪些部分以哪个顺序执行,以及使用哪些索引。这些选择实际上是“访问路径”,但最大的区别在于它们是由查询优化器自动生成的,而不是由程序员生成,所以我们很少需要考虑它们。 +在关系数据库中,查询优化器自动决定查询的哪些部分以哪个顺序执行,以及使用哪些索引。这些选择实际上是 “访问路径”,但最大的区别在于它们是由查询优化器自动生成的,而不是由程序员生成,所以我们很少需要考虑它们。 -如果想按新的方式查询数据,你可以声明一个新的索引,查询会自动使用最合适的那些索引。无需更改查询来利用新的索引(请参阅“[数据查询语言](#数据查询语言)”)。关系模型因此使添加应用程序新功能变得更加容易。 +如果想按新的方式查询数据,你可以声明一个新的索引,查询会自动使用最合适的那些索引。无需更改查询来利用新的索引(请参阅 “[数据查询语言](#数据查询语言)”)。关系模型因此使添加应用程序新功能变得更加容易。 -关系数据库的查询优化器是复杂的,已耗费了多年的研究和开发精力【18】。关系模型的一个关键洞察是:只需构建一次查询优化器,随后使用该数据库的所有应用程序都可以从中受益。如果你没有查询优化器的话,那么为特定查询手动编写访问路径比编写通用优化器更容易——不过从长期看通用解决方案更好。 +关系数据库的查询优化器是复杂的,已耗费了多年的研究和开发精力【18】。关系模型的一个关键洞察是:只需构建一次查询优化器,随后使用该数据库的所有应用程序都可以从中受益。如果你没有查询优化器的话,那么为特定查询手动编写访问路径比编写通用优化器更容易 —— 不过从长期看通用解决方案更好。 #### 与文档数据库相比 -在一个方面,文档数据库还原为层次模型:在其父记录中存储嵌套记录([图2-1](img/fig2-1.png)中的一对多关系,如`positions`,`education`和`contact_info`),而不是在单独的表中。 +在一个方面,文档数据库还原为层次模型:在其父记录中存储嵌套记录([图 2-1](img/fig2-1.png) 中的一对多关系,如 `positions`,`education` 和 `contact_info`),而不是在单独的表中。 -但是,在表示多对一和多对多的关系时,关系数据库和文档数据库并没有根本的不同:在这两种情况下,相关项目都被一个唯一的标识符引用,这个标识符在关系模型中被称为**外键**,在文档模型中称为**文档引用**【9】。该标识符在读取时通过连接或后续查询来解析。迄今为止,文档数据库没有走CODASYL的老路。 +但是,在表示多对一和多对多的关系时,关系数据库和文档数据库并没有根本的不同:在这两种情况下,相关项目都被一个唯一的标识符引用,这个标识符在关系模型中被称为 **外键**,在文档模型中称为 **文档引用**【9】。该标识符在读取时通过连接或后续查询来解析。迄今为止,文档数据库没有走 CODASYL 的老路。 ### 关系型数据库与文档数据库在今日的对比 -将关系数据库与文档数据库进行比较时,可以考虑许多方面的差异,包括它们的容错属性(请参阅[第五章](ch5.md))和处理并发性(请参阅[第七章](ch7.md))。本章将只关注数据模型中的差异。 +将关系数据库与文档数据库进行比较时,可以考虑许多方面的差异,包括它们的容错属性(请参阅 [第五章](ch5.md))和处理并发性(请参阅 [第七章](ch7.md))。本章将只关注数据模型中的差异。 支持文档数据模型的主要论据是架构灵活性,因局部性而拥有更好的性能,以及对于某些应用程序而言更接近于应用程序使用的数据结构。关系模型通过为连接提供更好的支持以及支持多对一和多对多的关系来反击。 #### 哪种数据模型更有助于简化应用代码? -如果应用程序中的数据具有类似文档的结构(即,一对多关系树,通常一次性加载整个树),那么使用文档模型可能是一个好主意。将类似文档的结构分解成多个表(如[图2-1](img/fig2-1.png)中的`positions`、`education`和`contact_info`)的关系技术可能导致繁琐的模式和不必要的复杂的应用程序代码。 +如果应用程序中的数据具有类似文档的结构(即,一对多关系树,通常一次性加载整个树),那么使用文档模型可能是一个好主意。将类似文档的结构分解成多个表(如 [图 2-1](img/fig2-1.png) 中的 `positions`、`education` 和 `contact_info`)的关系技术可能导致繁琐的模式和不必要的复杂的应用程序代码。 -文档模型有一定的局限性:例如,不能直接引用文档中的嵌套的项目,而是需要说“用户251的位置列表中的第二项”(很像层次模型中的访问路径)。但是,只要文件嵌套不太深,这通常不是问题。 +文档模型有一定的局限性:例如,不能直接引用文档中的嵌套的项目,而是需要说 “用户 251 的位置列表中的第二项”(很像层次模型中的访问路径)。但是,只要文件嵌套不太深,这通常不是问题。 文档数据库对连接的糟糕支持可能是个问题,也可能不是问题,这取决于应用程序。例如,如果某分析型应用程序使用一个文档数据库来记录何时何地发生了何事,那么多对多关系可能永远也用不上。【19】。 但如果你的应用程序确实会用到多对多关系,那么文档模型就没有那么诱人了。尽管可以通过反规范化来消除对连接的需求,但这需要应用程序代码来做额外的工作以确保数据一致性。尽管应用程序代码可以通过向数据库发出多个请求的方式来模拟连接,但这也将复杂性转移到应用程序中,而且通常也会比由数据库内的专用代码更慢。在这种情况下,使用文档模型可能会导致更复杂的应用代码与更差的性能【15】。 -我们没有办法说哪种数据模型更有助于简化应用代码,因为它取决于数据项之间的关系种类。对高度关联的数据而言,文档模型是极其糟糕的,关系模型是可以接受的,而选用图形模型(请参阅“[图数据模型](#图数据模型)”)是最自然的。 +我们没有办法说哪种数据模型更有助于简化应用代码,因为它取决于数据项之间的关系种类。对高度关联的数据而言,文档模型是极其糟糕的,关系模型是可以接受的,而选用图形模型(请参阅 “[图数据模型](#图数据模型)”)是最自然的。 #### 文档模型中的模式灵活性 -大多数文档数据库以及关系数据库中的JSON支持都不会强制文档中的数据采用何种模式。关系数据库的XML支持通常带有可选的模式验证。没有模式意味着可以将任意的键和值添加到文档中,并且当读取时,客户端对无法保证文档可能包含的字段。 +大多数文档数据库以及关系数据库中的 JSON 支持都不会强制文档中的数据采用何种模式。关系数据库的 XML 支持通常带有可选的模式验证。没有模式意味着可以将任意的键和值添加到文档中,并且当读取时,客户端对无法保证文档可能包含的字段。 -文档数据库有时称为**无模式(schemaless)**,但这具有误导性,因为读取数据的代码通常假定某种结构——即存在隐式模式,但不由数据库强制执行【20】。一个更精确的术语是**读时模式**(即schema-on-read,数据的结构是隐含的,只有在数据被读取时才被解释),相应的是**写时模式**(即schema-on-write,传统的关系数据库方法中,模式明确,且数据库确保所有的数据都符合其模式)【21】。 +文档数据库有时称为 **无模式(schemaless)**,但这具有误导性,因为读取数据的代码通常假定某种结构 —— 即存在隐式模式,但不由数据库强制执行【20】。一个更精确的术语是 **读时模式**(即 schema-on-read,数据的结构是隐含的,只有在数据被读取时才被解释),相应的是 **写时模式**(即 schema-on-write,传统的关系数据库方法中,模式明确,且数据库确保所有的数据都符合其模式)【21】。 读时模式类似于编程语言中的动态(运行时)类型检查,而写时模式类似于静态(编译时)类型检查。就像静态和动态类型检查的相对优点具有很大的争议性一样【22】,数据库中模式的强制性是一个具有争议的话题,一般来说没有正确或错误的答案。 @@ -253,7 +253,7 @@ if (user && user.name && !user.first_name) { } ``` -另一方面,在“静态类型”数据库模式中,通常会执行以下 **迁移(migration)** 操作: +另一方面,在 “静态类型” 数据库模式中,通常会执行以下 **迁移(migration)** 操作: ```sql ALTER TABLE users ADD COLUMN first_name text; @@ -261,11 +261,11 @@ UPDATE users SET first_name = split_part(name, ' ', 1); -- PostgreSQL UPDATE users SET first_name = substring_index(name, ' ', 1); -- MySQL ``` -模式变更的速度很慢,而且要求停运。它的这种坏名誉并不是完全应得的:大多数关系数据库系统可在几毫秒内执行`ALTER TABLE`语句。MySQL是一个值得注意的例外,它执行`ALTER TABLE`时会复制整个表,这可能意味着在更改一个大型表时会花费几分钟甚至几个小时的停机时间,尽管存在各种工具来解决这个限制【24,25,26】。 +模式变更的速度很慢,而且要求停运。它的这种坏名誉并不是完全应得的:大多数关系数据库系统可在几毫秒内执行 `ALTER TABLE` 语句。MySQL 是一个值得注意的例外,它执行 `ALTER TABLE` 时会复制整个表,这可能意味着在更改一个大型表时会花费几分钟甚至几个小时的停机时间,尽管存在各种工具来解决这个限制【24,25,26】。 -大型表上运行`UPDATE`语句在任何数据库上都可能会很慢,因为每一行都需要重写。要是不可接受的话,应用程序可以将`first_name`设置为默认值`NULL`,并在读取时再填充,就像使用文档数据库一样。 +大型表上运行 `UPDATE` 语句在任何数据库上都可能会很慢,因为每一行都需要重写。要是不可接受的话,应用程序可以将 `first_name` 设置为默认值 `NULL`,并在读取时再填充,就像使用文档数据库一样。 -当由于某种原因(例如,数据是异构的)集合中的项目并不都具有相同的结构时,读时模式更具优势。例如,如果: +当由于某种原因(例如,数据是异构的)集合中的项目并不都具有相同的结构时,读时模式更具优势。例如,如果: * 存在许多不同类型的对象,将每种类型的对象放在自己的表中是不现实的。 * 数据的结构由外部系统决定。你无法控制外部系统且它随时可能变化。 @@ -274,32 +274,32 @@ UPDATE users SET first_name = substring_index(name, ' ', 1); -- MySQL #### 查询的数据局部性 -文档通常以单个连续字符串形式进行存储,编码为JSON、XML或其二进制变体(如MongoDB的BSON)。如果应用程序经常需要访问整个文档(例如,将其渲染至网页),那么存储局部性会带来性能优势。如果将数据分割到多个表中(如[图2-1](img/fig2-1.png)所示),则需要进行多次索引查找才能将其全部检索出来,这可能需要更多的磁盘查找并花费更多的时间。 +文档通常以单个连续字符串形式进行存储,编码为 JSON、XML 或其二进制变体(如 MongoDB 的 BSON)。如果应用程序经常需要访问整个文档(例如,将其渲染至网页),那么存储局部性会带来性能优势。如果将数据分割到多个表中(如 [图 2-1](img/fig2-1.png) 所示),则需要进行多次索引查找才能将其全部检索出来,这可能需要更多的磁盘查找并花费更多的时间。 局部性仅仅适用于同时需要文档绝大部分内容的情况。数据库通常需要加载整个文档,即使只访问其中的一小部分,这对于大型文档来说是很浪费的。更新文档时,通常需要整个重写。只有不改变文档大小的修改才可以容易地原地执行。因此,通常建议保持相对小的文档,并避免增加文档大小的写入【9】。这些性能限制大大减少了文档数据库的实用场景。 -值得指出的是,为了局部性而分组集合相关数据的想法并不局限于文档模型。例如,Google的Spanner数据库在关系数据模型中提供了同样的局部性属性,允许模式声明一个表的行应该交错(嵌套)在父表内【27】。Oracle类似地允许使用一个称为 **多表索引集群表(multi-table index cluster tables)** 的类似特性【28】。Bigtable数据模型(用于Cassandra和HBase)中的 **列族(column-family)** 概念与管理局部性的目的类似【29】。 +值得指出的是,为了局部性而分组集合相关数据的想法并不局限于文档模型。例如,Google 的 Spanner 数据库在关系数据模型中提供了同样的局部性属性,允许模式声明一个表的行应该交错(嵌套)在父表内【27】。Oracle 类似地允许使用一个称为 **多表索引集群表(multi-table index cluster tables)** 的类似特性【28】。Bigtable 数据模型(用于 Cassandra 和 HBase)中的 **列族(column-family)** 概念与管理局部性的目的类似【29】。 -在[第三章](ch3.md)将还会看到更多关于局部性的内容。 +在 [第三章](ch3.md) 将还会看到更多关于局部性的内容。 #### 文档和关系数据库的融合 -自2000年代中期以来,大多数关系数据库系统(MySQL除外)都已支持XML。这包括对XML文档进行本地修改的功能,以及在XML文档中进行索引和查询的功能。这允许应用程序使用那种与文档数据库应当使用的非常类似的数据模型。 +自 2000 年代中期以来,大多数关系数据库系统(MySQL 除外)都已支持 XML。这包括对 XML 文档进行本地修改的功能,以及在 XML 文档中进行索引和查询的功能。这允许应用程序使用那种与文档数据库应当使用的非常类似的数据模型。 -从9.3版本开始的PostgreSQL 【8】,从5.7版本开始的MySQL以及从版本10.5开始的IBM DB2【30】也对JSON文档提供了类似的支持级别。鉴于用在Web APIs的JSON流行趋势,其他关系数据库很可能会跟随他们的脚步并添加JSON支持。 +从 9.3 版本开始的 PostgreSQL 【8】,从 5.7 版本开始的 MySQL 以及从版本 10.5 开始的 IBM DB2【30】也对 JSON 文档提供了类似的支持级别。鉴于用在 Web APIs 的 JSON 流行趋势,其他关系数据库很可能会跟随他们的脚步并添加 JSON 支持。 -在文档数据库中,RethinkDB在其查询语言中支持类似关系的连接,一些MongoDB驱动程序可以自动解析数据库引用(有效地执行客户端连接,尽管这可能比在数据库中执行的连接慢,需要额外的网络往返,并且优化更少)。 +在文档数据库中,RethinkDB 在其查询语言中支持类似关系的连接,一些 MongoDB 驱动程序可以自动解析数据库引用(有效地执行客户端连接,尽管这可能比在数据库中执行的连接慢,需要额外的网络往返,并且优化更少)。 -随着时间的推移,关系数据库和文档数据库似乎变得越来越相似,这是一件好事:数据模型相互补充[^v],如果一个数据库能够处理类似文档的数据,并能够对其执行关系查询,那么应用程序就可以使用最符合其需求的功能组合。 +随着时间的推移,关系数据库和文档数据库似乎变得越来越相似,这是一件好事:数据模型相互补充 [^v],如果一个数据库能够处理类似文档的数据,并能够对其执行关系查询,那么应用程序就可以使用最符合其需求的功能组合。 关系模型和文档模型的混合是未来数据库一条很好的路线。 -[^v]: Codd对关系模型【1】的原始描述实际上允许在关系模式中与JSON文档非常相似。他称之为**非简单域(nonsimple domains)**。这个想法是,一行中的值不一定是一个像数字或字符串一样的原始数据类型,也可以是一个嵌套的关系(表),因此可以把一个任意嵌套的树结构作为一个值,这很像30年后添加到SQL中的JSON或XML支持。 +[^v]: Codd 对关系模型【1】的原始描述实际上允许在关系模式中与 JSON 文档非常相似。他称之为 **非简单域(nonsimple domains)**。这个想法是,一行中的值不一定是一个像数字或字符串一样的原始数据类型,也可以是一个嵌套的关系(表),因此可以把一个任意嵌套的树结构作为一个值,这很像 30 年后添加到 SQL 中的 JSON 或 XML 支持。 ## 数据查询语言 -当引入关系模型时,关系模型包含了一种查询数据的新方法:SQL是一种 **声明式** 查询语言,而IMS和CODASYL使用 **命令式** 代码来查询数据库。那是什么意思? +当引入关系模型时,关系模型包含了一种查询数据的新方法:SQL 是一种 **声明式** 查询语言,而 IMS 和 CODASYL 使用 **命令式** 代码来查询数据库。那是什么意思? 许多常用的编程语言是命令式的。例如,给定一个动物物种的列表,返回列表中的鲨鱼可以这样写: @@ -321,7 +321,7 @@ sharks = σ_{family = "sharks"}(animals) $$ σ(希腊字母西格玛)是选择操作符,只返回符合条件的动物,`family="shark"`。 -定义SQL时,它紧密地遵循关系代数的结构: +定义 SQL 时,它紧密地遵循关系代数的结构: ```sql SELECT * FROM animals WHERE family ='Sharks'; @@ -329,21 +329,21 @@ SELECT * FROM animals WHERE family ='Sharks'; 命令式语言告诉计算机以特定顺序执行某些操作。可以想象一下,逐行地遍历代码,评估条件,更新变量,并决定是否再循环一遍。 -在声明式查询语言(如SQL或关系代数)中,你只需指定所需数据的模式 - 结果必须符合哪些条件,以及如何将数据转换(例如,排序,分组和集合) - 但不是如何实现这一目标。数据库系统的查询优化器决定使用哪些索引和哪些连接方法,以及以何种顺序执行查询的各个部分。 +在声明式查询语言(如 SQL 或关系代数)中,你只需指定所需数据的模式 - 结果必须符合哪些条件,以及如何将数据转换(例如,排序,分组和集合) - 但不是如何实现这一目标。数据库系统的查询优化器决定使用哪些索引和哪些连接方法,以及以何种顺序执行查询的各个部分。 -声明式查询语言是迷人的,因为它通常比命令式API更加简洁和容易。但更重要的是,它还隐藏了数据库引擎的实现细节,这使得数据库系统可以在无需对查询做任何更改的情况下进行性能提升。 +声明式查询语言是迷人的,因为它通常比命令式 API 更加简洁和容易。但更重要的是,它还隐藏了数据库引擎的实现细节,这使得数据库系统可以在无需对查询做任何更改的情况下进行性能提升。 例如,在本节开头所示的命令代码中,动物列表以特定顺序出现。如果数据库想要在后台回收未使用的磁盘空间,则可能需要移动记录,这会改变动物出现的顺序。数据库能否安全地执行,而不会中断查询? -SQL示例不确保任何特定的顺序,因此不在意顺序是否改变。但是如果查询用命令式的代码来写的话,那么数据库就永远不可能确定代码是否依赖于排序。SQL相当有限的功能性为数据库提供了更多自动优化的空间。 +SQL 示例不确保任何特定的顺序,因此不在意顺序是否改变。但是如果查询用命令式的代码来写的话,那么数据库就永远不可能确定代码是否依赖于排序。SQL 相当有限的功能性为数据库提供了更多自动优化的空间。 -最后,声明式语言往往适合并行执行。现在,CPU的速度通过核心(core)的增加变得更快,而不是以比以前更高的时钟速度运行【31】。命令代码很难在多个核心和多个机器之间并行化,因为它指定了指令必须以特定顺序执行。声明式语言更具有并行执行的潜力,因为它们仅指定结果的模式,而不指定用于确定结果的算法。在适当情况下,数据库可以自由使用查询语言的并行实现【32】。 +最后,声明式语言往往适合并行执行。现在,CPU 的速度通过核心(core)的增加变得更快,而不是以比以前更高的时钟速度运行【31】。命令代码很难在多个核心和多个机器之间并行化,因为它指定了指令必须以特定顺序执行。声明式语言更具有并行执行的潜力,因为它们仅指定结果的模式,而不指定用于确定结果的算法。在适当情况下,数据库可以自由使用查询语言的并行实现【32】。 -### Web上的声明式查询 +### Web 上的声明式查询 -声明式查询语言的优势不仅限于数据库。为了说明这一点,让我们在一个完全不同的环境中比较声明式和命令式方法:一个Web浏览器。 +声明式查询语言的优势不仅限于数据库。为了说明这一点,让我们在一个完全不同的环境中比较声明式和命令式方法:一个 Web 浏览器。 -假设你有一个关于海洋动物的网站。用户当前正在查看鲨鱼页面,因此你将当前所选的导航项目“鲨鱼”标记为当前选中项目。 +假设你有一个关于海洋动物的网站。用户当前正在查看鲨鱼页面,因此你将当前所选的导航项目 “鲨鱼” 标记为当前选中项目。 ```html ``` -现在想让当前所选页面的标题具有一个蓝色的背景,以便在视觉上突出显示。使用CSS实现起来非常简单: +现在想让当前所选页面的标题具有一个蓝色的背景,以便在视觉上突出显示。使用 CSS 实现起来非常简单: ```css li.selected > p { @@ -373,9 +373,9 @@ li.selected > p { } ``` -这里的CSS选择器`li.selected> p`声明了我们想要应用蓝色样式的元素的模式:即其直接父元素是具有`selected`CSS类的`
  • `元素的所有`

    `元素。示例中的元素`

    Sharks

    `匹配此模式,但`

    Whales

    `不匹配,因为其`
  • `父元素缺少`class =“selected”`。 +这里的 CSS 选择器 `li.selected > p` 声明了我们想要应用蓝色样式的元素的模式:即其直接父元素是具有 CSS 类 `selected` 的 `
  • ` 元素的所有 `

    ` 元素。示例中的元素 `

    Sharks

    ` 匹配此模式,但 `

    Whales

    ` 不匹配,因为其 `
  • ` 父元素缺少 `class="selected"`。 -如果使用XSL而不是CSS,你可以做类似的事情: +如果使用 XSL 而不是 CSS,你可以做类似的事情: ```xml @@ -385,9 +385,9 @@ li.selected > p { ``` -这里的XPath表达式`li[@class='selected']/p`相当于上例中的CSS选择器`li.selected> p`。CSS和XSL的共同之处在于,它们都是用于指定文档样式的声明式语言。 +这里的 XPath 表达式 `li[@class='selected']/p` 相当于上例中的 CSS 选择器 `li.selected > p`。CSS 和 XSL 的共同之处在于,它们都是用于指定文档样式的声明式语言。 -想象一下,必须使用命令式方法的情况会是如何。在Javascript中,使用 **文档对象模型(DOM)** API,其结果可能如下所示: +想象一下,必须使用命令式方法的情况会是如何。在 Javascript 中,使用 **文档对象模型(DOM)** API,其结果可能如下所示: ```js var liElements = document.getElementsByTagName("li"); @@ -404,27 +404,27 @@ for (var i = 0; i < liElements.length; i++) { } ``` -这段JavaScript代码命令式地将元素设置为蓝色背景,但是代码看起来很糟糕。不仅比CSS和XSL等价物更长,更难理解,而且还有一些严重的问题: +这段 JavaScript 代码命令式地将元素设置为蓝色背景,但是代码看起来很糟糕。不仅比 CSS 和 XSL 等价物更长,更难理解,而且还有一些严重的问题: -* 如果选定的类被移除(例如,因为用户点击了不同的页面),即使代码重新运行,蓝色背景也不会被移除 - 因此该项目将保持突出显示,直到整个页面被重新加载。使用CSS,浏览器会自动检测`li.selected> p`规则何时不再适用,并在选定的类被移除后立即移除蓝色背景。 +* 如果选定的类被移除(例如,因为用户点击了不同的页面),即使代码重新运行,蓝色背景也不会被移除 - 因此该项目将保持突出显示,直到整个页面被重新加载。使用 CSS,浏览器会自动检测 `li.selected > p` 规则何时不再适用,并在选定的类被移除后立即移除蓝色背景。 -* 如果你想要利用新的API(例如`document.getElementsBy ClassName(“selected”`)甚至`document.evaluate()`)来提高性能,则必须重写代码。另一方面,浏览器供应商可以在不破坏兼容性的情况下提高CSS和XPath的性能。 +* 如果你想要利用新的 API(例如 `document.getElementsByClassName("selected")` 甚至 `document.evaluate()`)来提高性能,则必须重写代码。另一方面,浏览器供应商可以在不破坏兼容性的情况下提高 CSS 和 XPath 的性能。 -在Web浏览器中,使用声明式CSS样式比使用JavaScript命令式地操作样式要好得多。类似地,在数据库中,使用像SQL这样的声明式查询语言比使用命令式查询API要好得多[^vi]。 +在 Web 浏览器中,使用声明式 CSS 样式比使用 JavaScript 命令式地操作样式要好得多。类似地,在数据库中,使用像 SQL 这样的声明式查询语言比使用命令式查询 API 要好得多 [^vi]。 -[^vi]: IMS和CODASYL都使用命令式API。应用程序通常使用COBOL代码遍历数据库中的记录,一次一条记录【2,16】。 +[^vi]: IMS 和 CODASYL 都使用命令式 API。应用程序通常使用 COBOL 代码遍历数据库中的记录,一次一条记录【2,16】。 -### MapReduce查询 +### MapReduce 查询 -MapReduce是一个由Google推广的编程模型,用于在多台机器上批量处理大规模的数据【33】。一些NoSQL数据存储(包括MongoDB和CouchDB)支持有限形式的MapReduce,作为在多个文档中执行只读查询的机制。 +MapReduce 是一个由 Google 推广的编程模型,用于在多台机器上批量处理大规模的数据【33】。一些 NoSQL 数据存储(包括 MongoDB 和 CouchDB)支持有限形式的 MapReduce,作为在多个文档中执行只读查询的机制。 -MapReduce将[第十章](ch10.md)中有更详细的描述。现在我们将简要讨论一下MongoDB使用的模型。 +MapReduce 将 [第十章](ch10.md) 中有更详细的描述。现在我们将简要讨论一下 MongoDB 使用的模型。 -MapReduce既不是一个声明式的查询语言,也不是一个完全命令式的查询API,而是处于两者之间:查询的逻辑用代码片段来表示,这些代码片段会被处理框架重复性调用。它基于`map`(也称为`collect`)和`reduce`(也称为`fold`或`inject`)函数,两个函数存在于许多函数式编程语言中。 +MapReduce 既不是一个声明式的查询语言,也不是一个完全命令式的查询 API,而是处于两者之间:查询的逻辑用代码片段来表示,这些代码片段会被处理框架重复性调用。它基于 `map`(也称为 `collect`)和 `reduce`(也称为 `fold` 或 `inject`)函数,两个函数存在于许多函数式编程语言中。 -最好举例来解释MapReduce模型。假设你是一名海洋生物学家,每当你看到海洋中的动物时,你都会在数据库中添加一条观察记录。现在你想生成一个报告,说明你每月看到多少鲨鱼。 +最好举例来解释 MapReduce 模型。假设你是一名海洋生物学家,每当你看到海洋中的动物时,你都会在数据库中添加一条观察记录。现在你想生成一个报告,说明你每月看到多少鲨鱼。 -在PostgreSQL中,你可以像这样表述这个查询: +在 PostgreSQL 中,你可以像这样表述这个查询: ```sql SELECT @@ -435,11 +435,11 @@ WHERE family = 'Sharks' GROUP BY observation_month; ``` -`date_trunc('month',timestamp)`函数用于确定包含`timestamp`的日历月份,并返回代表该月份开始的另一个时间戳。换句话说,它将时间戳舍入成最近的月份。 +`date_trunc('month',timestamp)` 函数用于确定包含 `timestamp` 的日历月份,并返回代表该月份开始的另一个时间戳。换句话说,它将时间戳舍入成最近的月份。 这个查询首先过滤观察记录,以只显示鲨鱼家族的物种,然后根据它们发生的日历月份对观察记录果进行分组,最后将在该月的所有观察记录中看到的动物数目加起来。 -同样的查询用MongoDB的MapReduce功能可以按如下来表述: +同样的查询用 MongoDB 的 MapReduce 功能可以按如下来表述: ```js db.observations.mapReduce(function map() { @@ -458,14 +458,14 @@ db.observations.mapReduce(function map() { }); ``` -* 可以声明式地指定一个只考虑鲨鱼种类的过滤器(这是MongoDB特定的MapReduce扩展)。 -* 每个匹配查询的文档都会调用一次JavaScript函数`map`,将`this`设置为文档对象。 -* `map`函数发出一个键(包括年份和月份的字符串,如`"2013-12"`或`"2014-1"`)和一个值(该观察记录中的动物数量)。 -* `map`发出的键值对按键来分组。对于具有相同键(即,相同的月份和年份)的所有键值对,调用一次`reduce`函数。 -* `reduce`函数将特定月份内所有观测记录中的动物数量相加。 -* 将最终的输出写入到`monthlySharkReport`集合中。 +* 可以声明式地指定一个只考虑鲨鱼种类的过滤器(这是 MongoDB 特定的 MapReduce 扩展)。 +* 每个匹配查询的文档都会调用一次 JavaScript 函数 `map`,将 `this` 设置为文档对象。 +* `map` 函数发出一个键(包括年份和月份的字符串,如 `"2013-12"` 或 `"2014-1"`)和一个值(该观察记录中的动物数量)。 +* `map` 发出的键值对按键来分组。对于具有相同键(即,相同的月份和年份)的所有键值对,调用一次 `reduce` 函数。 +* `reduce` 函数将特定月份内所有观测记录中的动物数量相加。 +* 将最终的输出写入到 `monthlySharkReport` 集合中。 -例如,假设`observations`集合包含这两个文档: +例如,假设 `observations` 集合包含这两个文档: ```json { @@ -482,15 +482,15 @@ db.observations.mapReduce(function map() { } ``` -对每个文档都会调用一次`map`函数,结果将是`emit("1995-12",3)`和`emit("1995-12",4)`。随后,以`reduce("1995-12",[3,4])`调用`reduce`函数,将返回`7`。 +对每个文档都会调用一次 `map` 函数,结果将是 `emit("1995-12",3)` 和 `emit("1995-12",4)`。随后,以 `reduce("1995-12",[3,4])` 调用 `reduce` 函数,将返回 `7`。 -map和reduce函数在功能上有所限制:它们必须是**纯**函数,这意味着它们只使用传递给它们的数据作为输入,它们不能执行额外的数据库查询,也不能有任何副作用。这些限制允许数据库以任何顺序运行任何功能,并在失败时重新运行它们。然而,map和reduce函数仍然是强大的:它们可以解析字符串,调用库函数,执行计算等等。 +map 和 reduce 函数在功能上有所限制:它们必须是 **纯** 函数,这意味着它们只使用传递给它们的数据作为输入,它们不能执行额外的数据库查询,也不能有任何副作用。这些限制允许数据库以任何顺序运行任何功能,并在失败时重新运行它们。然而,map 和 reduce 函数仍然是强大的:它们可以解析字符串,调用库函数,执行计算等等。 -MapReduce是一个相当底层的编程模型,用于计算机集群上的分布式执行。像SQL这样的更高级的查询语言可以用一系列的MapReduce操作来实现(见[第十章](ch10.md)),但是也有很多不使用MapReduce的分布式SQL实现。请注意,SQL中没有任何内容限制它在单个机器上运行,而MapReduce在分布式查询执行上没有垄断权。 +MapReduce 是一个相当底层的编程模型,用于计算机集群上的分布式执行。像 SQL 这样的更高级的查询语言可以用一系列的 MapReduce 操作来实现(见 [第十章](ch10.md)),但是也有很多不使用 MapReduce 的分布式 SQL 实现。请注意,SQL 中没有任何内容限制它在单个机器上运行,而 MapReduce 在分布式查询执行上没有垄断权。 -能够在查询中使用JavaScript代码是高级查询的一个重要特性,但这不限于MapReduce,一些SQL数据库也可以用JavaScript函数进行扩展【34】。 +能够在查询中使用 JavaScript 代码是高级查询的一个重要特性,但这不限于 MapReduce,一些 SQL 数据库也可以用 JavaScript 函数进行扩展【34】。 -MapReduce的一个可用性问题是,必须编写两个密切合作的JavaScript函数,这通常比编写单个查询更困难。此外,声明式查询语言为查询优化器提供了更多机会来提高查询的性能。基于这些原因,MongoDB 2.2添加了一种叫做**聚合管道**的声明式查询语言的支持【9】。用这种语言表述鲨鱼计数查询如下所示: +MapReduce 的一个可用性问题是,必须编写两个密切合作的 JavaScript 函数,这通常比编写单个查询更困难。此外,声明式查询语言为查询优化器提供了更多机会来提高查询的性能。基于这些原因,MongoDB 2.2 添加了一种叫做 **聚合管道** 的声明式查询语言的支持【9】。用这种语言表述鲨鱼计数查询如下所示: ```js db.observations.aggregate([ @@ -504,7 +504,7 @@ db.observations.aggregate([ ]); ``` -聚合管道语言与SQL的子集具有类似表现力,但是它使用基于JSON的语法而不是SQL的英语句子式语法; 这种差异也许是口味问题。这个故事的寓意是NoSQL系统可能会发现自己意外地重新发明了SQL,尽管带着伪装。 +聚合管道语言与 SQL 的子集具有类似表现力,但是它使用基于 JSON 的语法而不是 SQL 的英语句子式语法;这种差异也许是口味问题。这个故事的寓意是 NoSQL 系统可能会发现自己意外地重新发明了 SQL,尽管带着伪装。 ## 图数据模型 @@ -513,7 +513,7 @@ db.observations.aggregate([ 但是,要是多对多关系在你的数据中很常见呢?关系模型可以处理多对多关系的简单情况,但是随着数据之间的连接变得更加复杂,将数据建模为图形显得更加自然。 -一个图由两种对象组成:**顶点**(vertices,也称为**节点**,即nodes,或**实体**,即entities),和**边**(edges,也称为**关系**,即relationships,或**弧**,即arcs)。多种数据可以被建模为一个图形。典型的例子包括: +一个图由两种对象组成:**顶点**(vertices,也称为 **节点**,即 nodes,或 **实体**,即 entities),和 **边**(edges,也称为 **关系**,即 relationships,或 **弧**,即 arcs)。多种数据可以被建模为一个图形。典型的例子包括: * 社交图谱 @@ -521,23 +521,23 @@ db.observations.aggregate([ * 网络图谱 - 顶点是网页,边缘表示指向其他页面的HTML链接。 + 顶点是网页,边缘表示指向其他页面的 HTML 链接。 * 公路或铁路网络 顶点是交叉路口,边线代表它们之间的道路或铁路线。 -可以将那些众所周知的算法运用到这些图上:例如,汽车导航系统搜索道路网络中两点之间的最短路径,PageRank可以用在网络图上来确定网页的流行程度,从而确定该网页在搜索结果中的排名。 +可以将那些众所周知的算法运用到这些图上:例如,汽车导航系统搜索道路网络中两点之间的最短路径,PageRank 可以用在网络图上来确定网页的流行程度,从而确定该网页在搜索结果中的排名。 -在刚刚给出的例子中,图中的所有顶点代表了相同类型的事物(人、网页或交叉路口)。不过,图并不局限于这样的同类数据:同样强大地是,图提供了一种一致的方式,用来在单个数据存储中存储完全不同类型的对象。例如,Facebook维护一个包含许多不同类型的顶点和边的单个图:顶点表示人,地点,事件,签到和用户的评论;边缘表示哪些人是彼此的朋友,哪个签到发生在何处,谁评论了哪条消息,谁参与了哪个事件,等等【35】。 +在刚刚给出的例子中,图中的所有顶点代表了相同类型的事物(人、网页或交叉路口)。不过,图并不局限于这样的同类数据:同样强大地是,图提供了一种一致的方式,用来在单个数据存储中存储完全不同类型的对象。例如,Facebook 维护一个包含许多不同类型的顶点和边的单个图:顶点表示人,地点,事件,签到和用户的评论;边缘表示哪些人是彼此的朋友,哪个签到发生在何处,谁评论了哪条消息,谁参与了哪个事件,等等【35】。 -在本节中,我们将使用[图2-5](img/fig2-5.png)所示的示例。它可以从社交网络或系谱数据库中获得:它显示了两个人,来自爱达荷州的Lucy和来自法国Beaune的Alain。他们已婚,住在伦敦。 +在本节中,我们将使用 [图 2-5](img/fig2-5.png) 所示的示例。它可以从社交网络或系谱数据库中获得:它显示了两个人,来自爱达荷州的 Lucy 和来自法国 Beaune 的 Alain。他们已婚,住在伦敦。 ![](img/fig2-5.png) -**图2-5 图数据结构示例(框代表顶点,箭头代表边)** +**图 2-5 图数据结构示例(框代表顶点,箭头代表边)** -有几种不同但相关的方法用来构建和查询图表中的数据。在本节中,我们将讨论属性图模型(由Neo4j,Titan和InfiniteGraph实现)和三元组存储(triple-store)模型(由Datomic,AllegroGraph等实现)。我们将查看图的三种声明式查询语言:Cypher,SPARQL和Datalog。除此之外,还有像Gremlin 【36】这样的图形查询语言和像Pregel这样的图形处理框架(见[第十章](ch10.md))。 +有几种不同但相关的方法用来构建和查询图表中的数据。在本节中,我们将讨论属性图模型(由 Neo4j,Titan 和 InfiniteGraph 实现)和三元组存储(triple-store)模型(由 Datomic,AllegroGraph 等实现)。我们将查看图的三种声明式查询语言:Cypher,SPARQL 和 Datalog。除此之外,还有像 Gremlin 【36】这样的图形查询语言和像 Pregel 这样的图形处理框架(见 [第十章](ch10.md))。 ### 属性图 @@ -551,14 +551,14 @@ db.observations.aggregate([ 每条边(edge)包括: * 唯一标识符 -* 边的起点(**尾部顶点**,即tail vertex) -* 边的终点(**头部顶点**,即head vertex) +* 边的起点(**尾部顶点**,即 tail vertex) +* 边的终点(**头部顶点**,即 head vertex) * 描述两个顶点之间关系类型的标签 * 一组属性(键值对) -可以将图存储看作由两个关系表组成:一个存储顶点,另一个存储边,如[例2-2]()所示(该模式使用PostgreSQL JSON数据类型来存储每个顶点或每条边的属性)。头部和尾部顶点用来存储每条边;如果你想要一组顶点的输入或输出边,你可以分别通过`head_vertex`或`tail_vertex`来查询`edges`表。 +可以将图存储看作由两个关系表组成:一个存储顶点,另一个存储边,如 [例 2-2]() 所示(该模式使用 PostgreSQL JSON 数据类型来存储每个顶点或每条边的属性)。头部和尾部顶点用来存储每条边;如果你想要一组顶点的输入或输出边,你可以分别通过 `head_vertex` 或 `tail_vertex` 来查询 `edges` 表。 -**例2-2 使用关系模式来表示属性图** +**例 2-2 使用关系模式来表示属性图** ```sql CREATE TABLE vertices ( @@ -581,20 +581,20 @@ CREATE INDEX edges_heads ON edges (head_vertex); 关于这个模型的一些重要方面是: 1. 任何顶点都可以有一条边连接到任何其他顶点。没有模式限制哪种事物可不可以关联。 -2. 给定任何顶点,可以高效地找到它的入边和出边,从而遍历图,即沿着一系列顶点的路径前后移动(这就是为什么[例2-2]()在`tail_vertex`和`head_vertex`列上都有索引的原因)。 +2. 给定任何顶点,可以高效地找到它的入边和出边,从而遍历图,即沿着一系列顶点的路径前后移动(这就是为什么 [例 2-2]() 在 `tail_vertex` 和 `head_vertex` 列上都有索引的原因)。 3. 通过对不同类型的关系使用不同的标签,可以在一个图中存储几种不同的信息,同时仍然保持一个清晰的数据模型。 -这些特性为数据建模提供了很大的灵活性,如[图2-5](img/fig2-5.png)所示。图中显示了一些传统关系模式难以表达的事情,例如不同国家的不同地区结构(法国有省和州,美国有不同的州和州),国中国的怪事(先忽略主权国家和国家错综复杂的烂摊子),不同的数据粒度(Lucy现在的住所被指定为一个城市,而她的出生地点只是在一个州的级别)。 +这些特性为数据建模提供了很大的灵活性,如 [图 2-5](img/fig2-5.png) 所示。图中显示了一些传统关系模式难以表达的事情,例如不同国家的不同地区结构(法国有省和州,美国有不同的州和州),国中国的怪事(先忽略主权国家和国家错综复杂的烂摊子),不同的数据粒度(Lucy 现在的住所被指定为一个城市,而她的出生地点只是在一个州的级别)。 -你可以想象延伸图还能包括许多关于Lucy和Alain,或其他人的其他更多的事实。例如,你可以用它来表示食物过敏(为每个过敏源增加一个顶点,并增加人与过敏源之间的一条边来指示一种过敏情况),并链接到过敏源,每个过敏源具有一组顶点用来显示哪些食物含有哪些物质。然后,你可以写一个查询,找出每个人吃什么是安全的。图表在可演化性是富有优势的:当向应用程序添加功能时,可以轻松扩展图以适应应用程序数据结构的变化。 +你可以想象延伸图还能包括许多关于 Lucy 和 Alain,或其他人的其他更多的事实。例如,你可以用它来表示食物过敏(为每个过敏源增加一个顶点,并增加人与过敏源之间的一条边来指示一种过敏情况),并链接到过敏源,每个过敏源具有一组顶点用来显示哪些食物含有哪些物质。然后,你可以写一个查询,找出每个人吃什么是安全的。图表在可演化性是富有优势的:当向应用程序添加功能时,可以轻松扩展图以适应应用程序数据结构的变化。 -### Cypher查询语言 +### Cypher 查询语言 -Cypher是属性图的声明式查询语言,为Neo4j图形数据库而发明【37】(它是以电影“黑客帝国”中的一个角色来命名的,而与密码术中的密码无关【38】)。 +Cypher 是属性图的声明式查询语言,为 Neo4j 图形数据库而发明【37】(它是以电影 “黑客帝国” 中的一个角色来命名的,而与密码术中的密码无关【38】)。 -[例2-3]()显示了将[图2-5](img/fig2-5.png)的左边部分插入图形数据库的Cypher查询。可以类似地添加图的其余部分,为了便于阅读而省略。每个顶点都有一个像`USA`或`Idaho`这样的符号名称,查询的其他部分可以使用这些名称在顶点之间创建边,使用箭头符号:`(Idaho) - [:WITHIN] ->(USA)`创建一条标记为`WITHIN`的边,`Idaho`为尾节点,`USA`为头节点。 +[例 2-3]() 显示了将 [图 2-5](img/fig2-5.png) 的左边部分插入图形数据库的 Cypher 查询。可以类似地添加图的其余部分,为了便于阅读而省略。每个顶点都有一个像 `USA` 或 `Idaho` 这样的符号名称,查询的其他部分可以使用这些名称在顶点之间创建边,使用箭头符号:`(Idaho) - [:WITHIN] ->(USA)` 创建一条标记为 `WITHIN` 的边,`Idaho` 为尾节点,`USA` 为头节点。 -**例2-3 将图2-5中的数据子集表示为Cypher查询** +**例 2-3 将图 2-5 中的数据子集表示为 Cypher 查询** ```cypher CREATE @@ -606,11 +606,11 @@ CREATE (Lucy) -[:BORN_IN]-> (Idaho) ``` -当[图2-5](img/fig2-5.png)的所有顶点和边被添加到数据库后,让我们提些有趣的问题:例如,找到所有从美国移民到欧洲的人的名字。更确切地说,这里我们想要找到符合下面条件的所有顶点,并且返回这些顶点的`name`属性:该顶点拥有一条连到美国任一位置的`BORN_IN`边,和一条连到欧洲的任一位置的`LIVING_IN`边。 +当 [图 2-5](img/fig2-5.png) 的所有顶点和边被添加到数据库后,让我们提些有趣的问题:例如,找到所有从美国移民到欧洲的人的名字。更确切地说,这里我们想要找到符合下面条件的所有顶点,并且返回这些顶点的 `name` 属性:该顶点拥有一条连到美国任一位置的 `BORN_IN` 边,和一条连到欧洲的任一位置的 `LIVING_IN` 边。 -[例2-4]()展示了如何在Cypher中表达这个查询。在MATCH子句中使用相同的箭头符号来查找图中的模式:`(person) -[:BORN_IN]-> ()` 可以匹配`BORN_IN`边的任意两个顶点。该边的尾节点被绑定了变量`person`,头节点则未被绑定。 +[例 2-4]() 展示了如何在 Cypher 中表达这个查询。在 MATCH 子句中使用相同的箭头符号来查找图中的模式:`(person) -[:BORN_IN]-> ()` 可以匹配 `BORN_IN` 边的任意两个顶点。该边的尾节点被绑定了变量 `person`,头节点则未被绑定。 -**例2-4 查找所有从美国移民到欧洲的人的Cypher查询:** +**例 2-4 查找所有从美国移民到欧洲的人的 Cypher 查询:** ```cypher MATCH @@ -621,36 +621,36 @@ RETURN person.name 查询按如下来解读: -> 找到满足以下两个条件的所有顶点(称之为person顶点): -> 1. `person`顶点拥有一条到某个顶点的`BORN_IN`出边。从那个顶点开始,沿着一系列`WITHIN`出边最终到达一个类型为`Location`,`name`属性为`United States`的顶点。 +> 找到满足以下两个条件的所有顶点(称之为 person 顶点): +> 1. `person` 顶点拥有一条到某个顶点的 `BORN_IN` 出边。从那个顶点开始,沿着一系列 `WITHIN` 出边最终到达一个类型为 `Location`,`name` 属性为 `United States` 的顶点。 > -> 2. `person`顶点还拥有一条`LIVES_IN`出边。沿着这条边,可以通过一系列`WITHIN`出边最终到达一个类型为`Location`,`name`属性为`Europe`的顶点。 +> 2. `person` 顶点还拥有一条 `LIVES_IN` 出边。沿着这条边,可以通过一系列 `WITHIN` 出边最终到达一个类型为 `Location`,`name` 属性为 `Europe` 的顶点。 > -> 对于这样的`Person`顶点,返回其`name`属性。 +> 对于这样的 `Person` 顶点,返回其 `name` 属性。 执行这条查询可能会有几种可行的查询路径。这里给出的描述建议首先扫描数据库中的所有人,检查每个人的出生地和居住地,然后只返回符合条件的那些人。 -等价地,也可以从两个`Location`顶点开始反向地查找。假如`name`属性上有索引,则可以高效地找到代表美国和欧洲的两个顶点。然后,沿着所有`WITHIN`入边,可以继续查找出所有在美国和欧洲的位置(州,地区,城市等)。最后,查找出那些可以由`BORN_IN`或`LIVES_IN`入边到那些位置顶点的人。 +等价地,也可以从两个 `Location` 顶点开始反向地查找。假如 `name` 属性上有索引,则可以高效地找到代表美国和欧洲的两个顶点。然后,沿着所有 `WITHIN` 入边,可以继续查找出所有在美国和欧洲的位置(州,地区,城市等)。最后,查找出那些可以由 `BORN_IN` 或 `LIVES_IN` 入边到那些位置顶点的人。 通常对于声明式查询语言来说,在编写查询语句时,不需要指定执行细节:查询优化程序会自动选择预测效率最高的策略,因此你可以继续编写应用程序的其他部分。 -### SQL中的图查询 +### SQL 中的图查询 -[例2-2]()建议在关系数据库中表示图数据。但是,如果把图数据放入关系结构中,我们是否也可以使用SQL查询它? +[例 2-2]() 建议在关系数据库中表示图数据。但是,如果把图数据放入关系结构中,我们是否也可以使用 SQL 查询它? 答案是肯定的,但有些困难。在关系数据库中,你通常会事先知道在查询中需要哪些连接。在图查询中,你可能需要在找到待查找的顶点之前,遍历可变数量的边。也就是说,连接的数量事先并不确定。 -在我们的例子中,这发生在Cypher查询中的`() -[:WITHIN*0..]-> ()`规则中。一个人的`LIVES_IN`边可以指向任何类型的位置:街道、城市、地区、地区、国家等。一个城市可以在(WITHIN)一个地区内,一个地区可以在(WITHIN)在一个州内,一个州可以在(WITHIN)一个国家内,等等。`LIVES_IN`边可以直接指向正在查找的位置,或者一个在位置层次结构中隔了数层的位置。 +在我们的例子中,这发生在 Cypher 查询中的 `() -[:WITHIN*0..]-> ()` 规则中。一个人的 `LIVES_IN` 边可以指向任何类型的位置:街道、城市、地区、地区、国家等。一个城市可以在(WITHIN)一个地区内,一个地区可以在(WITHIN)在一个州内,一个州可以在(WITHIN)一个国家内,等等。`LIVES_IN` 边可以直接指向正在查找的位置,或者一个在位置层次结构中隔了数层的位置。 -在Cypher中,用`WITHIN*0..`非常简洁地表述了上述事实:“沿着`WITHIN`边,零次或多次”。它很像正则表达式中的`*`运算符。 +在 Cypher 中,用 `WITHIN*0..` 非常简洁地表述了上述事实:“沿着 `WITHIN` 边,零次或多次”。它很像正则表达式中的 `*` 运算符。 -自SQL:1999,查询可变长度遍历路径的思想可以使用称为**递归公用表表达式**(`WITH RECURSIVE`语法)的东西来表示。[例2-5]()显示了同样的查询 - 查找从美国移民到欧洲的人的姓名 - 在SQL使用这种技术(PostgreSQL、IBM DB2、Oracle和SQL Server均支持)来表述。但是,与Cypher相比,其语法非常笨拙。 +自 SQL:1999,查询可变长度遍历路径的思想可以使用称为 **递归公用表表达式**(`WITH RECURSIVE` 语法)的东西来表示。[例 2-5]() 显示了同样的查询 - 查找从美国移民到欧洲的人的姓名 - 在 SQL 使用这种技术(PostgreSQL、IBM DB2、Oracle 和 SQL Server 均支持)来表述。但是,与 Cypher 相比,其语法非常笨拙。 -**例2-5 与示例2-4同样的查询,在SQL中使用递归公用表表达式表示** +**例 2-5 与示例 2-4 同样的查询,在 SQL 中使用递归公用表表达式表示** ```sql WITH RECURSIVE - -- in_usa 包含所有的美国境内的位置ID + -- in_usa 包含所有的美国境内的位置 ID in_usa(vertex_id) AS ( SELECT vertex_id FROM vertices WHERE properties ->> 'name' = 'United States' UNION @@ -658,7 +658,7 @@ WITH RECURSIVE JOIN in_usa ON edges.head_vertex = in_usa.vertex_id WHERE edges.label = 'within' ), - -- in_europe 包含所有的欧洲境内的位置ID + -- in_europe 包含所有的欧洲境内的位置 ID in_europe(vertex_id) AS ( SELECT vertex_id FROM vertices WHERE properties ->> 'name' = 'Europe' UNION @@ -666,13 +666,13 @@ WITH RECURSIVE JOIN in_europe ON edges.head_vertex = in_europe.vertex_id WHERE edges.label = 'within' ), - -- born_in_usa 包含了所有类型为Person,且出生在美国的顶点 + -- born_in_usa 包含了所有类型为 Person,且出生在美国的顶点 born_in_usa(vertex_id) AS ( SELECT edges.tail_vertex FROM edges JOIN in_usa ON edges.head_vertex = in_usa.vertex_id WHERE edges.label = 'born_in' ), - -- lives_in_europe 包含了所有类型为Person,且居住在欧洲的顶点。 + -- lives_in_europe 包含了所有类型为 Person,且居住在欧洲的顶点。 lives_in_europe(vertex_id) AS ( SELECT edges.tail_vertex FROM edges JOIN in_europe ON edges.head_vertex = in_europe.vertex_id @@ -684,29 +684,29 @@ WITH RECURSIVE JOIN lives_in_europe ON vertices.vertex_id = lives_in_europe.vertex_id; ``` -* 首先,查找`name`属性为`United States`的顶点,将其作为`in_usa`顶点的集合的第一个元素。 -* 从`in_usa`集合的顶点出发,沿着所有的`with_in`入边,将其尾顶点加入同一集合,不断递归直到所有`with_in`入边都被访问完毕。 -* 同理,从`name`属性为`Europe`的顶点出发,建立`in_europe`顶点的集合。 -* 对于`in_usa`集合中的每个顶点,根据`born_in`入边来查找出生在美国某个地方的人。 -* 同样,对于`in_europe`集合中的每个顶点,根据`lives_in`入边来查找居住在欧洲的人。 +* 首先,查找 `name` 属性为 `United States` 的顶点,将其作为 `in_usa` 顶点的集合的第一个元素。 +* 从 `in_usa` 集合的顶点出发,沿着所有的 `with_in` 入边,将其尾顶点加入同一集合,不断递归直到所有 `with_in` 入边都被访问完毕。 +* 同理,从 `name` 属性为 `Europe` 的顶点出发,建立 `in_europe` 顶点的集合。 +* 对于 `in_usa` 集合中的每个顶点,根据 `born_in` 入边来查找出生在美国某个地方的人。 +* 同样,对于 `in_europe` 集合中的每个顶点,根据 `lives_in` 入边来查找居住在欧洲的人。 * 最后,把在美国出生的人的集合与在欧洲居住的人的集合相交。 -同一个查询,用某一个查询语言可以写成4行,而用另一个查询语言需要29行,这恰恰说明了不同的数据模型是为不同的应用场景而设计的。选择适合应用程序的数据模型非常重要。 +同一个查询,用某一个查询语言可以写成 4 行,而用另一个查询语言需要 29 行,这恰恰说明了不同的数据模型是为不同的应用场景而设计的。选择适合应用程序的数据模型非常重要。 -### 三元组存储和SPARQL +### 三元组存储和 SPARQL 三元组存储模式大体上与属性图模型相同,用不同的词来描述相同的想法。不过仍然值得讨论,因为三元组存储有很多现成的工具和语言,这些工具和语言对于构建应用程序的工具箱可能是宝贵的补充。 -在三元组存储中,所有信息都以非常简单的三部分表示形式存储(**主语**,**谓语**,**宾语**)。例如,三元组 **(吉姆, 喜欢 ,香蕉)** 中,**吉姆** 是主语,**喜欢** 是谓语(动词),**香蕉** 是对象。 +在三元组存储中,所有信息都以非常简单的三部分表示形式存储(**主语**,**谓语**,**宾语**)。例如,三元组 **(吉姆, 喜欢, 香蕉)** 中,**吉姆** 是主语,**喜欢** 是谓语(动词),**香蕉** 是对象。 三元组的主语相当于图中的一个顶点。而宾语是下面两者之一: -1. 原始数据类型中的值,例如字符串或数字。在这种情况下,三元组的谓语和宾语相当于主语顶点上的属性的键和值。例如,`(lucy, age, 33)`就像属性`{“age”:33}`的顶点lucy。 -2. 图中的另一个顶点。在这种情况下,谓语是图中的一条边,主语是其尾部顶点,而宾语是其头部顶点。例如,在`(lucy, marriedTo, alain)`中主语和宾语`lucy`和`alain`都是顶点,并且谓语`marriedTo`是连接他们的边的标签。 +1. 原始数据类型中的值,例如字符串或数字。在这种情况下,三元组的谓语和宾语相当于主语顶点上的属性的键和值。例如,`(lucy, age, 33)` 就像属性 `{“age”:33}` 的顶点 lucy。 +2. 图中的另一个顶点。在这种情况下,谓语是图中的一条边,主语是其尾部顶点,而宾语是其头部顶点。例如,在 `(lucy, marriedTo, alain)` 中主语和宾语 `lucy` 和 `alain` 都是顶点,并且谓语 `marriedTo` 是连接他们的边的标签。 -[例2-6]()显示了与[例2-3]()相同的数据,以称为Turtle的格式(Notation3(N3)【39】)的一个子集形式写成三元组。 +[例 2-6]() 显示了与 [例 2-3]() 相同的数据,以称为 Turtle 的格式(Notation3(N3)【39】)的一个子集形式写成三元组。 -**例2-6 图2-5中的数据子集,表示为Turtle三元组** +**例 2-6 图 2-5 中的数据子集,表示为 Turtle 三元组** ```reStructuredText @prefix : . @@ -726,11 +726,11 @@ _:namerica :name "North America" _:namerica :type :"continent" ``` -在这个例子中,图的顶点被写为:`_:someName`。这个名字并不意味着这个文件以外的任何东西。它的存在只是帮助我们明确哪些三元组引用了同一顶点。当谓语表示边时,该宾语是一个顶点,如`_:idaho :within _:usa.`。当谓语是一个属性时,该宾语是一个字符串,如`_:usa :name "United States"` +在这个例子中,图的顶点被写为:`_:someName`。这个名字并不意味着这个文件以外的任何东西。它的存在只是帮助我们明确哪些三元组引用了同一顶点。当谓语表示边时,该宾语是一个顶点,如 `_:idaho :within _:usa.`。当谓语是一个属性时,该宾语是一个字符串,如 `_:usa :name"United States"` -一遍又一遍地重复相同的主语看起来相当重复,但幸运的是,可以使用分号来说明关于同一主语的多个事情。这使得Turtle格式相当不错,可读性强:请参阅[例2-7]()。 +一遍又一遍地重复相同的主语看起来相当重复,但幸运的是,可以使用分号来说明关于同一主语的多个事情。这使得 Turtle 格式相当不错,可读性强:请参阅 [例 2-7]()。 -**例2-7 一种相对例2-6写入数据的更为简洁的方法。** +**例 2-7 一种相对例 2-6 写入数据的更为简洁的方法。** ``` @prefix : . @@ -742,21 +742,21 @@ _:namerica a :Location; :name "North America"; :type "continent". #### 语义网 -如果你阅读更多关于三元组存储的信息,你可能会被卷入关于语义网的文章漩涡中。三元组存储数据模型完全独立于语义网,例如,Datomic【40】是三元组存储[^vii],并没有声称与它有任何关系。但是,由于在很多人眼中这两者紧密相连,我们应该简要地讨论一下。 +如果你阅读更多关于三元组存储的信息,你可能会被卷入关于语义网的文章漩涡中。三元组存储数据模型完全独立于语义网,例如,Datomic【40】是三元组存储 [^vii],并没有声称与它有任何关系。但是,由于在很多人眼中这两者紧密相连,我们应该简要地讨论一下。 -[^vii]: 从技术上讲,Datomic使用的是五元组而不是三元组,两个额外的字段是用于版本控制的元数据 +[^vii]: 从技术上讲,Datomic 使用的是五元组而不是三元组,两个额外的字段是用于版本控制的元数据 -从本质上讲语义网是一个简单且合理的想法:网站已经将信息发布为文字和图片供人类阅读,为什么不将信息作为机器可读的数据也发布给计算机呢?**资源描述框架**(RDF)【41】的目的是作为不同网站以统一的格式发布数据的一种机制,允许来自不同网站的数据自动合并成**一个数据网络** - 一种互联网范围内的“通用语义网数据库“。 +从本质上讲语义网是一个简单且合理的想法:网站已经将信息发布为文字和图片供人类阅读,为什么不将信息作为机器可读的数据也发布给计算机呢?**资源描述框架**(RDF)【41】的目的是作为不同网站以统一的格式发布数据的一种机制,允许来自不同网站的数据自动合并成 **一个数据网络** - 一种互联网范围内的 “通用语义网数据库 “。 不幸的是,这个语义网在二十一世纪初被过度使用,但到目前为止没有任何迹象表明已在实践中实现,这使得许多人嗤之以鼻。它还遭受了过多的令人眼花缭乱的缩略词,过于复杂的标准提议和狂妄自大的苦果。 -然而,如果从过去的失败中汲取教训,语义网项目还是拥有很多优秀的成果。即使你没有兴趣在语义网上发布RDF数据,三元组这种模型也是一种好的应用程序内部数据模型。 +然而,如果从过去的失败中汲取教训,语义网项目还是拥有很多优秀的成果。即使你没有兴趣在语义网上发布 RDF 数据,三元组这种模型也是一种好的应用程序内部数据模型。 -#### RDF数据模型 +#### RDF 数据模型 -[例2-7]()中使用的Turtle语言是一种用于RDF数据的人类可读格式。有时候,RDF也可以以XML格式编写,不过完成同样的事情会相对啰嗦,请参阅[例2-8]()。Turtle/N3是更可取的,因为它更容易阅读,像Apache Jena 【42】这样的工具可以根据需要在不同的RDF格式之间进行自动转换。 +[例 2-7]() 中使用的 Turtle 语言是一种用于 RDF 数据的人类可读格式。有时候,RDF 也可以以 XML 格式编写,不过完成同样的事情会相对啰嗦,请参阅 [例 2-8]()。Turtle/N3 是更可取的,因为它更容易阅读,像 Apache Jena 【42】这样的工具可以根据需要在不同的 RDF 格式之间进行自动转换。 -**例2-8 用RDF/XML语法表示例2-7的数据** +**例 2-8 用 RDF/XML 语法表示例 2-7 的数据** ```xml ``` -RDF有一些奇怪之处,因为它是为了在互联网上交换数据而设计的。三元组的主语,谓语和宾语通常是URI。例如,谓语可能是一个URI,如 ``或``,而不仅仅是`WITHIN`或`LIVES_IN`。这个设计背后的原因为了让你能够把你的数据和其他人的数据结合起来,如果他们赋予单词`within`或者`lives_in`不同的含义,两者也不会冲突,因为它们的谓语实际上是``和``。 +RDF 有一些奇怪之处,因为它是为了在互联网上交换数据而设计的。三元组的主语,谓语和宾语通常是 URI。例如,谓语可能是一个 URI,如 `` 或 ``,而不仅仅是 `WITHIN` 或 `LIVES_IN`。这个设计背后的原因为了让你能够把你的数据和其他人的数据结合起来,如果他们赋予单词 `within` 或者 `lives_in` 不同的含义,两者也不会冲突,因为它们的谓语实际上是 `` 和 ``。 -从RDF的角度来看,URL `` 不一定需要能解析成什么东西,它只是一个命名空间。为避免与`http://URL`混淆,本节中的示例使用不可解析的URI,如`urn:example:within`。幸运的是,你只需在文件顶部指定一个前缀,然后就不用再管了。 +从 RDF 的角度来看,URL `` 不一定需要能解析成什么东西,它只是一个命名空间。为避免与 `http://URL` 混淆,本节中的示例使用不可解析的 URI,如 `urn:example:within`。幸运的是,你只需在文件顶部指定一个前缀,然后就不用再管了。 -### SPARQL查询语言 +### SPARQL 查询语言 -**SPARQL**是一种用于三元组存储的面向RDF数据模型的查询语言【43】(它是SPARQL协议和RDF查询语言的缩写,发音为“sparkle”)。SPARQL早于Cypher,并且由于Cypher的模式匹配借鉴于SPARQL,这使得它们看起来非常相似【37】。 +**SPARQL** 是一种用于三元组存储的面向 RDF 数据模型的查询语言【43】(它是 SPARQL 协议和 RDF 查询语言的缩写,发音为 “sparkle”)。SPARQL 早于 Cypher,并且由于 Cypher 的模式匹配借鉴于 SPARQL,这使得它们看起来非常相似【37】。 -与之前相同的查询 - 查找从美国转移到欧洲的人 - 使用SPARQL比使用Cypher甚至更为简洁(请参阅[例2-9]())。 +与之前相同的查询 - 查找从美国转移到欧洲的人 - 使用 SPARQL 比使用 Cypher 甚至更为简洁(请参阅 [例 2-9]())。 -**例2-9 与示例2-4相同的查询,用SPARQL表示** +**例 2-9 与示例 2-4 相同的查询,用 SPARQL 表示** ```sparql PREFIX : @@ -805,46 +805,46 @@ SELECT ?personName WHERE { } ``` -结构非常相似。以下两个表达式是等价的(SPARQL中的变量以问号开头): +结构非常相似。以下两个表达式是等价的(SPARQL 中的变量以问号开头): ``` (person) -[:BORN_IN]-> () -[:WITHIN*0..]-> (location) # Cypher ?person :bornIn / :within* ?location. # SPARQL ``` -因为RDF不区分属性和边,而只是将它们作为谓语,所以可以使用相同的语法来匹配属性。在下面的表达式中,变量`usa`被绑定到任意具有值为字符串`"United States"`的`name`属性的顶点: +因为 RDF 不区分属性和边,而只是将它们作为谓语,所以可以使用相同的语法来匹配属性。在下面的表达式中,变量 `usa` 被绑定到任意具有值为字符串 `"United States"` 的 `name` 属性的顶点: ``` (usa {name:'United States'}) # Cypher ?usa :name "United States". # SPARQL ``` -SPARQL是一种很好的查询语言—尽管SPARQL从未实现语义网,但是它仍然是一种应用程序内部使用的强大工具。 +SPARQL 是一种很好的查询语言 — 尽管 SPARQL 从未实现语义网,但是它仍然是一种应用程序内部使用的强大工具。 > #### 图形数据库与网状模型相比较 > -> 在“[文档数据库是否在重蹈覆辙?](#文档数据库是否在重蹈覆辙?)”中,我们讨论了CODASYL和关系模型如何竞相解决IMS中的多对多关系问题。乍一看,CODASYL的网状模型看起来与图模型相似。CODASYL是否是图形数据库的第二个变种? +> 在 “[文档数据库是否在重蹈覆辙?](#文档数据库是否在重蹈覆辙?)” 中,我们讨论了 CODASYL 和关系模型如何竞相解决 IMS 中的多对多关系问题。乍一看,CODASYL 的网状模型看起来与图模型相似。CODASYL 是否是图形数据库的第二个变种? > > 不,他们在几个重要方面有所不同: > -> * 在CODASYL中,数据库有一个模式,用于指定哪种记录类型可以嵌套在其他记录类型中。在图形数据库中,不存在这样的限制:任何顶点都可以具有到其他任何顶点的边。这为应用程序适应不断变化的需求提供了更大的灵活性。 -> * 在CODASYL中,达到特定记录的唯一方法是遍历其中的一个访问路径。在图形数据库中,可以通过其唯一ID直接引用任何顶点,也可以使用索引来查找具有特定值的顶点。 -> * 在CODASYL,记录的后续是一个有序集合,所以数据库的人不得不维持排序(这会影响存储布局),并且插入新记录到数据库的应用程序不得不担心的新记录在这些集合中的位置。在图形数据库中,顶点和边不是有序的(只能在查询时对结果进行排序)。 -> * 在CODASYL中,所有查询都是命令式的,难以编写,并且很容易因架构中的变化而受到破坏。在图形数据库中,如果需要,可以在命令式代码中编写遍历,但大多数图形数据库也支持高级声明式查询语言,如Cypher或SPARQL。 +> * 在 CODASYL 中,数据库有一个模式,用于指定哪种记录类型可以嵌套在其他记录类型中。在图形数据库中,不存在这样的限制:任何顶点都可以具有到其他任何顶点的边。这为应用程序适应不断变化的需求提供了更大的灵活性。 +> * 在 CODASYL 中,达到特定记录的唯一方法是遍历其中的一个访问路径。在图形数据库中,可以通过其唯一 ID 直接引用任何顶点,也可以使用索引来查找具有特定值的顶点。 +> * 在 CODASYL,记录的后续是一个有序集合,所以数据库的人不得不维持排序(这会影响存储布局),并且插入新记录到数据库的应用程序不得不担心的新记录在这些集合中的位置。在图形数据库中,顶点和边不是有序的(只能在查询时对结果进行排序)。 +> * 在 CODASYL 中,所有查询都是命令式的,难以编写,并且很容易因架构中的变化而受到破坏。在图形数据库中,如果需要,可以在命令式代码中编写遍历,但大多数图形数据库也支持高级声明式查询语言,如 Cypher 或 SPARQL。 > > ### 基础:Datalog -**Datalog**是比SPARQL、Cypher更古老的语言,在20世纪80年代被学者广泛研究【44,45,46】。它在软件工程师中不太知名,但是它是重要的,因为它为以后的查询语言提供了基础。 +**Datalog** 是比 SPARQL、Cypher 更古老的语言,在 20 世纪 80 年代被学者广泛研究【44,45,46】。它在软件工程师中不太知名,但是它是重要的,因为它为以后的查询语言提供了基础。 -在实践中,Datalog被用于少数的数据系统中:例如,它是Datomic 【40】的查询语言,Cascalog 【47】是一种用于查询Hadoop大数据集的Datalog实现[^viii]。 +在实践中,Datalog 被用于少数的数据系统中:例如,它是 Datomic 【40】的查询语言,Cascalog 【47】是一种用于查询 Hadoop 大数据集的 Datalog 实现 [^viii]。 -[^viii]: Datomic和Cascalog使用Datalog的Clojure S表达式语法。在下面的例子中使用了一个更容易阅读的Prolog语法,但两者没有任何功能差异。 +[^viii]: Datomic 和 Cascalog 使用 Datalog 的 Clojure S 表达式语法。在下面的例子中使用了一个更容易阅读的 Prolog 语法,但两者没有任何功能差异。 -Datalog的数据模型类似于三元组模式,但进行了一点泛化。把三元组写成**谓语**(**主语,宾语**),而不是写三元语(**主语,谓语,宾语**)。[例2-10]()显示了如何用Datalog写入我们的例子中的数据。 +Datalog 的数据模型类似于三元组模式,但进行了一点泛化。把三元组写成 **谓语**(**主语,宾语**),而不是写三元语(**主语,谓语,宾语**)。[例 2-10]() 显示了如何用 Datalog 写入我们的例子中的数据。 -**例2-10 用Datalog来表示图2-5中的数据子集** +**例 2-10 用 Datalog 来表示图 2-5 中的数据子集** ```prolog name(namerica, 'North America'). @@ -862,9 +862,9 @@ name(lucy, 'Lucy'). born_in(lucy, idaho). ``` -既然已经定义了数据,我们可以像之前一样编写相同的查询,如[例2-11]()所示。它看起来有点不同于Cypher或SPARQL的等价物,但是请不要放弃它。Datalog是Prolog的一个子集,如果你学过计算机科学,你可能已经见过。 +既然已经定义了数据,我们可以像之前一样编写相同的查询,如 [例 2-11]() 所示。它看起来有点不同于 Cypher 或 SPARQL 的等价物,但是请不要放弃它。Datalog 是 Prolog 的一个子集,如果你学过计算机科学,你可能已经见过。 -**例2-11 与示例2-4相同的查询,用Datalog表示** +**例 2-11 与示例 2-4 相同的查询,用 Datalog 表示** ``` within_recursive(Location, Name) :- name(Location, Name). /* Rule 1 */ @@ -881,51 +881,51 @@ migrated(Name, BornIn, LivingIn) :- name(Person, Name), /* Rule 3 */ ?- migrated(Who, 'United States', 'Europe'). /* Who = 'Lucy'. */ ``` -Cypher和SPARQL使用SELECT立即跳转,但是Datalog一次只进行一小步。我们定义**规则**,以将新谓语告诉数据库:在这里,我们定义了两个新的谓语,`within_recursive`和`migrated`。这些谓语不是存储在数据库中的三元组中,而是它们是从数据或其他规则派生而来的。规则可以引用其他规则,就像函数可以调用其他函数或者递归地调用自己一样。像这样,复杂的查询可以一次构建其中的一小块。 +Cypher 和 SPARQL 使用 SELECT 立即跳转,但是 Datalog 一次只进行一小步。我们定义 **规则**,以将新谓语告诉数据库:在这里,我们定义了两个新的谓语,`within_recursive` 和 `migrated`。这些谓语不是存储在数据库中的三元组中,而是它们是从数据或其他规则派生而来的。规则可以引用其他规则,就像函数可以调用其他函数或者递归地调用自己一样。像这样,复杂的查询可以一次构建其中的一小块。 -在规则中,以大写字母开头的单词是变量,谓语则用Cypher和SPARQL的方式一样来匹配。例如,`name(Location, Name)`通过变量绑定`Location = namerica`和`Name ='North America'`可以匹配三元组`name(namerica, 'North America')`。 +在规则中,以大写字母开头的单词是变量,谓语则用 Cypher 和 SPARQL 的方式一样来匹配。例如,`name(Location, Name)` 通过变量绑定 `Location = namerica` 和 `Name ='North America'` 可以匹配三元组 `name(namerica, 'North America')`。 -要是系统可以在`:-` 操作符的右侧找到与所有谓语的一个匹配,就运用该规则。当规则运用时,就好像通过`:-`的左侧将其添加到数据库(将变量替换成它们匹配的值)。 +要是系统可以在 `:-` 操作符的右侧找到与所有谓语的一个匹配,就运用该规则。当规则运用时,就好像通过 `:-` 的左侧将其添加到数据库(将变量替换成它们匹配的值)。 因此,一种可能的应用规则的方式是: -1. 数据库存在`name(namerica, 'North America')`,故运用规则1。它生成`within_recursive(namerica, 'North America')`。 -2. 数据库存在`within(usa, namerica)`,在上一步骤中生成`within_recursive(namerica, 'North America')`,故运用规则2。它会产生`within_recursive(usa, 'North America')`。 -3. 数据库存在`within(idaho, usa)`,在上一步生成`within_recursive(usa, 'North America')`,故运用规则2。它产生`within_recursive(idaho, 'North America')`。 +1. 数据库存在 `name (namerica, 'North America')`,故运用规则 1。它生成 `within_recursive (namerica, 'North America')`。 +2. 数据库存在 `within (usa, namerica)`,在上一步骤中生成 `within_recursive (namerica, 'North America')`,故运用规则 2。它会产生 `within_recursive (usa, 'North America')`。 +3. 数据库存在 `within (idaho, usa)`,在上一步生成 `within_recursive (usa, 'North America')`,故运用规则 2。它产生 `within_recursive (idaho, 'North America')`。 -通过重复应用规则1和2,`within_recursive`谓语可以告诉我们在数据库中包含北美(或任何其他位置名称)的所有位置。这个过程如[图2-6](img/fig2-6.png)所示。 +通过重复应用规则 1 和 2,`within_recursive` 谓语可以告诉我们在数据库中包含北美(或任何其他位置名称)的所有位置。这个过程如 [图 2-6](img/fig2-6.png) 所示。 ![](img/fig2-6.png) -**图2-6 使用示例2-11中的Datalog规则来确定爱达荷州在北美。** +**图 2-6 使用示例 2-11 中的 Datalog 规则来确定爱达荷州在北美。** -现在规则3可以找到出生在某个地方`BornIn`的人,并住在某个地方`LivingIn`。通过查询`BornIn ='United States'`和`LivingIn ='Europe'`,并将此人作为变量`Who`,让Datalog系统找出变量`Who`会出现哪些值。因此,最后得到了与早先的Cypher和SPARQL查询相同的答案。 +现在规则 3 可以找到出生在某个地方 `BornIn` 的人,并住在某个地方 `LivingIn`。通过查询 `BornIn ='United States'` 和 `LivingIn ='Europe'`,并将此人作为变量 `Who`,让 Datalog 系统找出变量 `Who` 会出现哪些值。因此,最后得到了与早先的 Cypher 和 SPARQL 查询相同的答案。 -相对于本章讨论的其他查询语言,我们需要采取不同的思维方式来思考Datalog方法,但这是一种非常强大的方法,因为规则可以在不同的查询中进行组合和重用。虽然对于简单的一次性查询,显得不太方便,但是它可以更好地处理数据很复杂的情况。 +相对于本章讨论的其他查询语言,我们需要采取不同的思维方式来思考 Datalog 方法,但这是一种非常强大的方法,因为规则可以在不同的查询中进行组合和重用。虽然对于简单的一次性查询,显得不太方便,但是它可以更好地处理数据很复杂的情况。 ## 本章小结 数据模型是一个巨大的课题,在本章中,我们快速浏览了各种不同的模型。我们没有足够的空间来详细介绍每个模型的细节,但是希望这个概述足以激起你的兴趣,以更多地了解最适合你的应用需求的模型。 -在历史上,数据最开始被表示为一棵大树(层次数据模型),但是这不利于表示多对多的关系,所以发明了关系模型来解决这个问题。最近,开发人员发现一些应用程序也不适合采用关系模型。新的非关系型“NoSQL”数据存储在两个主要方向上存在分歧: +在历史上,数据最开始被表示为一棵大树(层次数据模型),但是这不利于表示多对多的关系,所以发明了关系模型来解决这个问题。最近,开发人员发现一些应用程序也不适合采用关系模型。新的非关系型 “NoSQL” 数据存储在两个主要方向上存在分歧: -1. **文档数据库**的应用场景是:数据通常是自我包含的,而且文档之间的关系非常稀少。 -2. **图形数据库**用于相反的场景:任意事物都可能与任何事物相关联。 +1. **文档数据库** 的应用场景是:数据通常是自我包含的,而且文档之间的关系非常稀少。 +2. **图形数据库** 用于相反的场景:任意事物都可能与任何事物相关联。 这三种模型(文档,关系和图形)在今天都被广泛使用,并且在各自的领域都发挥很好。一个模型可以用另一个模型来模拟 — 例如,图数据可以在关系数据库中表示 — 但结果往往是糟糕的。这就是为什么我们有着针对不同目的的不同系统,而不是一个单一的万能解决方案。 文档数据库和图数据库有一个共同点,那就是它们通常不会为存储的数据强制一个模式,这可以使应用程序更容易适应不断变化的需求。但是应用程序很可能仍会假定数据具有一定的结构;这只是模式是明确的(写入时强制)还是隐含的(读取时处理)的问题。 -每个数据模型都具有各自的查询语言或框架,我们讨论了几个例子:SQL,MapReduce,MongoDB的聚合管道,Cypher,SPARQL和Datalog。我们也谈到了CSS和XSL/XPath,它们不是数据库查询语言,而包含有趣的相似之处。 +每个数据模型都具有各自的查询语言或框架,我们讨论了几个例子:SQL,MapReduce,MongoDB 的聚合管道,Cypher,SPARQL 和 Datalog。我们也谈到了 CSS 和 XSL/XPath,它们不是数据库查询语言,而包含有趣的相似之处。 虽然我们已经覆盖了很多层面,但仍然有许多数据模型没有提到。举几个简单的例子: -* 使用基因组数据的研究人员通常需要执行**序列相似性搜索**,这意味着需要一个很长的字符串(代表一个DNA分子),并在一个拥有类似但不完全相同的字符串的大型数据库中寻找匹配。这里所描述的数据库都不能处理这种用法,这就是为什么研究人员编写了像GenBank这样的专门的基因组数据库软件的原因【48】。 +* 使用基因组数据的研究人员通常需要执行 **序列相似性搜索**,这意味着需要一个很长的字符串(代表一个 DNA 分子),并在一个拥有类似但不完全相同的字符串的大型数据库中寻找匹配。这里所描述的数据库都不能处理这种用法,这就是为什么研究人员编写了像 GenBank 这样的专门的基因组数据库软件的原因【48】。 * 粒子物理学家数十年来一直在进行大数据类型的大规模数据分析,像大型强子对撞机(LHC)这样的项目现在可以工作在数百亿兆字节的范围内!在这样的规模下,需要定制解决方案来阻止硬件成本的失控【49】。 -* **全文搜索**可以说是一种经常与数据库一起使用的数据模型。信息检索是一个很大的专业课题,我们不会在本书中详细介绍,但是我们将在第三章和第三部分中介绍搜索索引。 +* **全文搜索** 可以说是一种经常与数据库一起使用的数据模型。信息检索是一个很大的专业课题,我们不会在本书中详细介绍,但是我们将在第三章和第三部分中介绍搜索索引。 -让我们暂时将其放在一边。在[下一章](ch3.md)中,我们将讨论在**实现**本章描述的数据模型时会遇到的一些权衡。 +让我们暂时将其放在一边。在 [下一章](ch3.md) 中,我们将讨论在 **实现** 本章描述的数据模型时会遇到的一些权衡。 ## 参考文献 @@ -981,4 +981,3 @@ Cypher和SPARQL使用SELECT立即跳转,但是Datalog一次只进行一小步 | 上一章 | 目录 | 下一章 | | -------------------------------------------- | ------------------------------- | ---------------------------- | | [第一章:可靠性、可伸缩性、可维护性](ch1.md) | [设计数据密集型应用](README.md) | [第三章:存储与检索](ch3.md) | - diff --git a/colophon.md b/colophon.md index b9e32544..e59b9ba1 100644 --- a/colophon.md +++ b/colophon.md @@ -2,9 +2,9 @@ ## 关于作者 -**Martin Kleppmann**是英国剑桥大学分布式系统的研究员。此前他曾在互联网公司担任过软件工程师和企业家,其中包括LinkedIn和Rapportive,负责大规模数据基础架构。在这个过程中,他以艰难的方式学习了一些东西,他希望这本书能够让你避免重蹈覆辙。 +**Martin Kleppmann** 是英国剑桥大学分布式系统的研究员。此前他曾在互联网公司担任过软件工程师和企业家,其中包括 LinkedIn 和 Rapportive,负责大规模数据基础架构。在这个过程中,他以艰难的方式学习了一些东西,他希望这本书能够让你避免重蹈覆辙。 -Martin是一位常规会议演讲者,博主和开源贡献者。他认为,每个人都应该有深刻的技术理念,深层次的理解能帮助我们开发出更好的软件。 +Martin 是一位常规会议演讲者,博主和开源贡献者。他认为,每个人都应该有深刻的技术理念,深层次的理解能帮助我们开发出更好的软件。 ![](http://martin.kleppmann.com/2017/03/ddia-poster.jpg) @@ -20,14 +20,14 @@ Alibaba+-Finplus 架构师/全栈工程师 (2015 ~ 2017) ## 后记 -《设计数据密集型应用》封面上的动物是**印度野猪(Sus scrofa cristatus)**,它是在印度、缅甸、尼泊尔、斯里兰卡和泰国发现的一种野猪的亚种。与欧洲野猪不同,它们有更高的背部鬃毛,没有体表绒毛,以及更大更直的头骨。 +《设计数据密集型应用》封面上的动物是 **印度野猪(Sus scrofa cristatus)**,它是在印度、缅甸、尼泊尔、斯里兰卡和泰国发现的一种野猪的亚种。与欧洲野猪不同,它们有更高的背部鬃毛,没有体表绒毛,以及更大更直的头骨。 -印度野猪有一头灰色或黑色的头发,脊背上有短而硬的毛。雄性有突出的犬齿(称为T),用来与对手战斗或抵御掠食者。雄性比雌性大,这些物种平均肩高33-35英寸,体重200-300磅。他们的天敌包括熊、老虎和各种大型猫科动物。 +印度野猪有一头灰色或黑色的头发,脊背上有短而硬的毛。雄性有突出的犬齿(称为 T),用来与对手战斗或抵御掠食者。雄性比雌性大,这些物种平均肩高 33-35 英寸,体重 200-300 磅。他们的天敌包括熊、老虎和各种大型猫科动物。 -这些动物夜行且杂食——它们吃各种各样的东西,包括根、昆虫、腐肉、坚果、浆果和小动物。野猪经常因为破坏农作物的根被人们所熟知,他们造成大量的破坏,并被农民所敌视。他们每天需要摄入4,000 ~ 4,500卡路里的能量。野猪有发达的嗅觉,这有助于寻找地下植物和挖掘动物。然而,它们的视力很差。 +这些动物夜行且杂食 —— 它们吃各种各样的东西,包括根、昆虫、腐肉、坚果、浆果和小动物。野猪经常因为破坏农作物的根被人们所熟知,他们造成大量的破坏,并被农民所敌视。他们每天需要摄入 4,000 ~ 4,500 卡路里的能量。野猪有发达的嗅觉,这有助于寻找地下植物和挖掘动物。然而,它们的视力很差。 野猪在人类文化中一直具有重要意义。在印度教传说中,野猪是毗湿奴神的化身。在古希腊的丧葬纪念碑中,它是一个勇敢失败者的象征(与胜利的狮子相反)。由于它的侵略,它被描绘在斯堪的纳维亚、日耳曼和盎格鲁撒克逊战士的盔甲和武器上。在中国十二生肖中,它象征着决心和急躁。 -O'Reilly封面上的许多动物都受到威胁,这些动物对世界都很重要。要了解有关如何提供帮助的更多信息,请访问animals.oreilly.com。 +O'Reilly 封面上的许多动物都受到威胁,这些动物对世界都很重要。要了解有关如何提供帮助的更多信息,请访问 animals.oreilly.com。 -封面图片来自Shaw's Zoology。封面字体是URW Typewriter和Guardian Sans。文字字体是Adobe Minion Pro;图中的字体是Adobe Myriad Pro;标题字体是Adobe Myriad Condensed;代码字体是Dalton Maag的Ubuntu Mono。 +封面图片来自 Shaw's Zoology。封面字体是 URW Typewriter 和 Guardian Sans。文字字体是 Adobe Minion Pro;图中的字体是 Adobe Myriad Pro;标题字体是 Adobe Myriad Condensed;代码字体是 Dalton Maag 的 Ubuntu Mono。 \ No newline at end of file diff --git a/glossary.md b/glossary.md index b1014a96..488cbbfb 100644 --- a/glossary.md +++ b/glossary.md @@ -5,7 +5,7 @@ * **异步(asynchronous)** - 不等待某些事情完成(例如,将数据发送到网络中的另一个节点),并且不会假设要花多长时间。请参阅[同步复制与异步复制](ch5.md#同步复制与异步复制)”,“[同步网络与异步网络](ch8.md#同步网络与异步网络)”,以及“[系统模型与现实](ch8.md#系统模型与现实)”。 + 不等待某些事情完成(例如,将数据发送到网络中的另一个节点),并且不会假设要花多长时间。请参阅“[同步复制与异步复制](ch5.md#同步复制与异步复制)”、“[同步网络与异步网络](ch8.md#同步网络与异步网络)”以及“[系统模型与现实](ch8.md#系统模型与现实)”。 * **原子(atomic)** diff --git a/part-i.md b/part-i.md index 8e82390f..92bf8116 100644 --- a/part-i.md +++ b/part-i.md @@ -1,13 +1,13 @@ -# 第一部分:数据系统的基石 +# 第一部分:数据系统的基石 本书前四章介绍了数据系统底层的基础概念,无论是在单台机器上运行的单点数据系统,还是分布在多台机器上的分布式数据系统都适用。 -1. [第一章](ch1.md)将介绍本书使用的术语和方法。**可靠性,可伸缩性和可维护性** ,这些词汇到底意味着什么?如何实现这些目标? -2. [第二章](ch2.md)将对几种不同的**数据模型和查询语言**进行比较。从程序员的角度看,这是数据库之间最明显的区别。不同的数据模型适用于不同的应用场景。 -3. [第三章](ch3.md)将深入**存储引擎**内部,研究数据库如何在磁盘上摆放数据。不同的存储引擎针对不同的负载进行优化,选择合适的存储引擎对系统性能有巨大影响。 -4. [第四章](ch4)将对几种不同的 **数据编码**进行比较。特别研究了这些格式在应用需求经常变化、模式需要随时间演变的环境中表现如何。 +1. [第一章](ch1.md) 将介绍本书使用的术语和方法。**可靠性,可伸缩性和可维护性** ,这些词汇到底意味着什么?如何实现这些目标? +2. [第二章](ch2.md) 将对几种不同的 **数据模型和查询语言** 进行比较。从程序员的角度看,这是数据库之间最明显的区别。不同的数据模型适用于不同的应用场景。 +3. [第三章](ch3.md) 将深入 **存储引擎** 内部,研究数据库如何在磁盘上摆放数据。不同的存储引擎针对不同的负载进行优化,选择合适的存储引擎对系统性能有巨大影响。 +4. [第四章](ch4) 将对几种不同的 **数据编码** 进行比较。特别研究了这些格式在应用需求经常变化、模式需要随时间演变的环境中表现如何。 -第二部分将专门讨论在**分布式数据系统**中特有的问题。 +第二部分将专门讨论在 **分布式数据系统** 中特有的问题。 ## 目录 @@ -16,7 +16,7 @@ 1. [可靠性、可伸缩性、可维护性](ch1.md) 2. [数据模型与查询语言](ch2.md) 3. [存储与检索](ch3.md) -4. [编码与演化](ch4.md) +4. [编码与演化](ch4.md) ------ diff --git a/part-ii.md b/part-ii.md index 3855b271..9ece30fe 100644 --- a/part-ii.md +++ b/part-ii.md @@ -1,13 +1,13 @@ -# 第二部分:分布式数据 +# 第二部分:分布式数据 > 一个成功的技术,现实的优先级必须高于公关,你可以糊弄别人,但糊弄不了自然规律。 > -> ——罗杰斯委员会报告(1986) +> —— 罗杰斯委员会报告(1986) > ------- -在本书的[第一部分](part-i.md)中,我们讨论了数据系统的各个方面,但仅限于数据存储在单台机器上的情况。现在我们到了[第二部分](part-ii.md),进入更高的层次,并提出一个问题:如果**多台机器**参与数据的存储和检索,会发生什么? +在本书的 [第一部分](part-i.md) 中,我们讨论了数据系统的各个方面,但仅限于数据存储在单台机器上的情况。现在我们到了 [第二部分](part-ii.md),进入更高的层次,并提出一个问题:如果 **多台机器** 参与数据的存储和检索,会发生什么? 你可能会出于各种各样的原因,希望将数据库分布到多台机器上: @@ -25,27 +25,27 @@ ## 伸缩至更高的载荷 -如果你需要的只是伸缩至更高的**载荷(load)**,最简单的方法就是购买更强大的机器(有时称为**垂直伸缩**,即vertical scaling,或**向上伸缩**,即scale up)。许多处理器,内存和磁盘可以在同一个操作系统下相互连接,快速的相互连接允许任意处理器访问内存或磁盘的任意部分。在这种 **共享内存架构(shared-memory architecture)** 中,所有的组件都可以看作一台单独的机器[^i]。 +如果你需要的只是伸缩至更高的 **载荷(load)**,最简单的方法就是购买更强大的机器(有时称为 **垂直伸缩**,即 vertical scaling,或 **向上伸缩**,即 scale up)。许多处理器,内存和磁盘可以在同一个操作系统下相互连接,快速的相互连接允许任意处理器访问内存或磁盘的任意部分。在这种 **共享内存架构(shared-memory architecture)** 中,所有的组件都可以看作一台单独的机器 [^i]。 -[^i]: 在大型机中,尽管任意处理器都可以访问内存的任意部分,但总有一些内存区域与一些处理器更接近(称为**非均匀内存访问(nonuniform memory access, NUMA)**【1】)。 为了有效利用这种架构特性,需要对处理进行细分,以便每个处理器主要访问临近的内存,这意味着即使表面上看起来只有一台机器在运行,**分区(partitioning)** 仍然是必要的。 +[^i]: 在大型机中,尽管任意处理器都可以访问内存的任意部分,但总有一些内存区域与一些处理器更接近(称为 **非均匀内存访问(nonuniform memory access, NUMA)**【1】)。 为了有效利用这种架构特性,需要对处理进行细分,以便每个处理器主要访问临近的内存,这意味着即使表面上看起来只有一台机器在运行,**分区(partitioning)** 仍然是必要的。 共享内存方法的问题在于,成本增长速度快于线性增长:一台有着双倍处理器数量,双倍内存大小,双倍磁盘容量的机器,通常成本会远远超过原来的两倍。而且可能因为存在瓶颈,并不足以处理双倍的载荷。 -共享内存架构可以提供有限的容错能力,高端机器可以使用热插拔的组件(不关机更换磁盘,内存模块,甚至处理器)——但它必然囿于单个地理位置的桎梏。 +共享内存架构可以提供有限的容错能力,高端机器可以使用热插拔的组件(不关机更换磁盘,内存模块,甚至处理器)—— 但它必然囿于单个地理位置的桎梏。 -另一种方法是**共享磁盘架构(shared-disk architecture)**,它使用多台具有独立处理器和内存的机器,但将数据存储在机器之间共享的磁盘阵列上,这些磁盘通过快速网络连接[^ii]。这种架构用于某些数据仓库,但竞争和锁定的开销限制了共享磁盘方法的可伸缩性【2】。 +另一种方法是 **共享磁盘架构(shared-disk architecture)**,它使用多台具有独立处理器和内存的机器,但将数据存储在机器之间共享的磁盘阵列上,这些磁盘通过快速网络连接 [^ii]。这种架构用于某些数据仓库,但竞争和锁定的开销限制了共享磁盘方法的可伸缩性【2】。 -[^ii]: 网络附属存储(Network Attached Storage, NAS),或**存储区网络(Storage Area Network, SAN)** +[^ii]: 网络附属存储(Network Attached Storage, NAS),或 **存储区网络(Storage Area Network, SAN)** ### 无共享架构 -相比之下,**无共享架构**【3】(shared-nothing architecture,有时被称为**水平伸缩**,即horizontal scaling,或**向外伸缩**,即scaling out)已经相当普及。在这种架构中,运行数据库软件的每台机器/虚拟机都称为**节点(node)**。每个节点只使用各自的处理器,内存和磁盘。节点之间的任何协调,都是在软件层面使用传统网络实现的。 +相比之下,**无共享架构**【3】(shared-nothing architecture,有时被称为 **水平伸缩**,即 horizontal scaling,或 **向外伸缩**,即 scaling out)已经相当普及。在这种架构中,运行数据库软件的每台机器 / 虚拟机都称为 **节点(node)**。每个节点只使用各自的处理器,内存和磁盘。节点之间的任何协调,都是在软件层面使用传统网络实现的。 -无共享系统不需要使用特殊的硬件,所以你可以用任意机器——比如性价比最好的机器。你也许可以跨多个地理区域分布数据从而减少用户延迟,或者在损失一整个数据中心的情况下幸免于难。随着云端虚拟机部署的出现,即使是小公司,现在无需Google级别的运维,也可以实现异地分布式架构。 +无共享系统不需要使用特殊的硬件,所以你可以用任意机器 —— 比如性价比最好的机器。你也许可以跨多个地理区域分布数据从而减少用户延迟,或者在损失一整个数据中心的情况下幸免于难。随着云端虚拟机部署的出现,即使是小公司,现在无需 Google 级别的运维,也可以实现异地分布式架构。 -在这一部分里,我们将重点放在无共享架构上。它不见得是所有场景的最佳选择,但它是最需要你谨慎从事的架构。如果你的数据分布在多个节点上,你需要意识到这样一个分布式系统中约束和权衡 ——数据库并不能魔术般地把这些东西隐藏起来。 +在这一部分里,我们将重点放在无共享架构上。它不见得是所有场景的最佳选择,但它是最需要你谨慎从事的架构。如果你的数据分布在多个节点上,你需要意识到这样一个分布式系统中约束和权衡 —— 数据库并不能魔术般地把这些东西隐藏起来。 -虽然分布式无共享架构有许多优点,但它通常也会给应用带来额外的复杂度,有时也会限制你可用数据模型的表达力。在某些情况下,一个简单的单线程程序可以比一个拥有超过100个CPU核的集群表现得更好【4】。另一方面,无共享系统可以非常强大。接下来的几章,将详细讨论分布式数据会带来的问题。 +虽然分布式无共享架构有许多优点,但它通常也会给应用带来额外的复杂度,有时也会限制你可用数据模型的表达力。在某些情况下,一个简单的单线程程序可以比一个拥有超过 100 个 CPU 核的集群表现得更好【4】。另一方面,无共享系统可以非常强大。接下来的几章,将详细讨论分布式数据会带来的问题。 ### 复制 vs 分区 @@ -53,30 +53,30 @@ * 复制(Replication) - 在几个不同的节点上保存数据的相同副本,可能放在不同的位置。 复制提供了冗余:如果一些节点不可用,剩余的节点仍然可以提供数据服务。 复制也有助于改善性能。 [第五章](ch5.md)将讨论复制。 + 在几个不同的节点上保存数据的相同副本,可能放在不同的位置。 复制提供了冗余:如果一些节点不可用,剩余的节点仍然可以提供数据服务。 复制也有助于改善性能。 [第五章](ch5.md) 将讨论复制。 * 分区 (Partitioning) - 将一个大型数据库拆分成较小的子集(称为**分区**,即partitions),从而不同的分区可以指派给不同的**节点**(nodes,亦称**分片**,即sharding)。 [第六章](ch6.md)将讨论分区。 + 将一个大型数据库拆分成较小的子集(称为 **分区**,即 partitions),从而不同的分区可以指派给不同的 **节点**(nodes,亦称 **分片**,即 sharding)。 [第六章](ch6.md) 将讨论分区。 -复制和分区是不同的机制,但它们经常同时使用。如[图II-1](img/figii-1.png)所示。 +复制和分区是不同的机制,但它们经常同时使用。如 [图 II-1](img/figii-1.png) 所示。 ![](img/figii-1.png) -**图II-1 一个数据库切分为两个分区,每个分区都有两个副本** +**图 II-1 一个数据库切分为两个分区,每个分区都有两个副本** -理解了这些概念,就可以开始讨论在分布式系统中需要做出的困难抉择。[第七章](ch7.md)将讨论**事务(Transaction)**,这对于了解数据系统中可能出现的各种问题,以及我们可以做些什么很有帮助。[第八章](ch8.md)和[第九章](ch9.md)将讨论分布式系统的根本局限性。 +理解了这些概念,就可以开始讨论在分布式系统中需要做出的困难抉择。[第七章](ch7.md) 将讨论 **事务 (Transaction)**,这对于了解数据系统中可能出现的各种问题,以及我们可以做些什么很有帮助。[第八章](ch8.md) 和 [第九章](ch9.md) 将讨论分布式系统的根本局限性。 -在本书的[第三部分](part-iii.md)中,将讨论如何将多个(可能是分布式的)数据存储集成为一个更大的系统,以满足复杂的应用需求。 但首先,我们来聊聊分布式的数据。 +在本书的 [第三部分](part-iii.md) 中,将讨论如何将多个(可能是分布式的)数据存储集成为一个更大的系统,以满足复杂的应用需求。 但首先,我们来聊聊分布式的数据。 ## 索引 5. [复制](ch5.md) -6. [分片](ch6.md) -7. [事务](ch7.md) -8. [分布式系统的麻烦](ch8.md) -9. [一致性与共识](ch9.md) +6. [分片](ch6.md) +7. [事务](ch7.md) +8. [分布式系统的麻烦](ch8.md) +9. [一致性与共识](ch9.md) ## 参考文献 diff --git a/part-iii.md b/part-iii.md index 8dfd62bd..b1192f72 100644 --- a/part-iii.md +++ b/part-iii.md @@ -1,6 +1,6 @@ # 第三部分:衍生数据 -在本书的[第一部分](part-i.md)和[第二部分](part-ii.md)中,我们自底向上地把所有关于分布式数据库的主要考量都过了一遍。从数据在磁盘上的布局,一直到出现故障时分布式系统一致性的局限。但所有的讨论都假定了应用中只用了一种数据库。 +在本书的 [第一部分](part-i.md) 和 [第二部分](part-ii.md) 中,我们自底向上地把所有关于分布式数据库的主要考量都过了一遍。从数据在磁盘上的布局,一直到出现故障时分布式系统一致性的局限。但所有的讨论都假定了应用中只用了一种数据库。 现实世界中的数据系统往往更为复杂。大型应用程序经常需要以多种方式访问和处理数据,没有一个数据库可以同时满足所有这些不同的需求。因此应用程序通常组合使用多种组件:数据存储,索引,缓存,分析系统,等等,并实现在这些组件中移动数据的机制。 @@ -12,15 +12,15 @@ * 记录系统(System of record) - **记录系统**,也被称为**真相源(source of truth)**,持有数据的权威版本。当新的数据进入时(例如,用户输入)首先会记录在这里。每个事实正正好好表示一次(表示通常是**正规化的**,即normalized)。如果其他系统和**记录系统**之间存在任何差异,那么记录系统中的值是正确的(根据定义)。 + **记录系统**,也被称为 **真相源(source of truth)**,持有数据的权威版本。当新的数据进入时(例如,用户输入)首先会记录在这里。每个事实正正好好表示一次(表示通常是 **正规化的**,即 normalized)。如果其他系统和 **记录系统** 之间存在任何差异,那么记录系统中的值是正确的(根据定义)。 * 衍生数据系统(Derived data systems) - **衍生系统**中的数据,通常是另一个系统中的现有数据以某种方式进行转换或处理的结果。如果丢失衍生数据,可以从原始来源重新创建。典型的例子是**缓存(cache)**:如果数据在缓存中,就可以由缓存提供服务;如果缓存不包含所需数据,则降级由底层数据库提供。非规范化的值,索引和物化视图亦属此类。在推荐系统中,预测汇总数据通常衍生自用户日志。 + **衍生系统** 中的数据,通常是另一个系统中的现有数据以某种方式进行转换或处理的结果。如果丢失衍生数据,可以从原始来源重新创建。典型的例子是 **缓存(cache)**:如果数据在缓存中,就可以由缓存提供服务;如果缓存不包含所需数据,则降级由底层数据库提供。非规范化的值,索引和物化视图亦属此类。在推荐系统中,预测汇总数据通常衍生自用户日志。 -从技术上讲,衍生数据是**冗余的(redundant)**,因为它重复了已有的信息。但是衍生数据对于获得良好的只读查询性能通常是至关重要的。它通常是非规范化的。可以从单个源头衍生出多个不同的数据集,使你能从不同的“视角”洞察数据。 +从技术上讲,衍生数据是 **冗余的(redundant)**,因为它重复了已有的信息。但是衍生数据对于获得良好的只读查询性能通常是至关重要的。它通常是非规范化的。可以从单个源头衍生出多个不同的数据集,使你能从不同的 “视角” 洞察数据。 -并不是所有的系统都在其架构中明确区分**记录系统**和**衍生数据系统**,但是这是一种有用的区分方式,因为它明确了系统中的数据流:系统的哪一部分具有哪些输入和哪些输出,以及它们如何相互依赖。 +并不是所有的系统都在其架构中明确区分 **记录系统** 和 **衍生数据系统**,但是这是一种有用的区分方式,因为它明确了系统中的数据流:系统的哪一部分具有哪些输入和哪些输出,以及它们如何相互依赖。 大多数数据库,存储引擎和查询语言,本质上既不是记录系统也不是衍生系统。数据库只是一个工具:如何使用它取决于你自己。**记录系统和衍生数据系统之间的区别不在于工具,而在于应用程序中的使用方式。** @@ -28,7 +28,7 @@ ## 章节概述 -我们将从[第十章](ch10.md)开始,研究例如MapReduce这样 **面向批处理(batch-oriented)** 的数据流系统。对于建设大规模数据系统,我们将看到,它们提供了优秀的工具和思想。[第十一章](ch11.md)将把这些思想应用到 **流式数据(data streams)** 中,使我们能用更低的延迟完成同样的任务。[第十二章](ch12.md)将对本书进行总结,探讨如何使用这些工具来构建可靠,可伸缩和可维护的应用。 +我们将从 [第十章](ch10.md) 开始,研究例如 MapReduce 这样 **面向批处理(batch-oriented)** 的数据流系统。对于建设大规模数据系统,我们将看到,它们提供了优秀的工具和思想。[第十一章](ch11.md) 将把这些思想应用到 **流式数据(data streams)** 中,使我们能用更低的延迟完成同样的任务。[第十二章](ch12.md) 将对本书进行总结,探讨如何使用这些工具来构建可靠,可伸缩和可维护的应用。 ## 索引 diff --git a/preface.md b/preface.md index 58beb277..22c4dc6f 100644 --- a/preface.md +++ b/preface.md @@ -1,6 +1,6 @@ # 序言 -如果近几年从业于软件工程,特别是服务器端和后端系统开发,那么你很有可能已经被大量关于数据存储和处理的时髦词汇轰炸过了: NoSQL!大数据!Web-Scale!分片!最终一致性!ACID! CAP定理!云服务!MapReduce!实时! +如果近几年从业于软件工程,特别是服务器端和后端系统开发,那么你很有可能已经被大量关于数据存储和处理的时髦词汇轰炸过了: NoSQL!大数据!Web-Scale!分片!最终一致性!ACID! CAP 定理!云服务!MapReduce!实时! 在最近十年中,我们看到了很多有趣的进展,关于数据库,分布式系统,以及在此基础上构建应用程序的方式。这些进展有着各种各样的驱动力: @@ -32,11 +32,11 @@ 本书是为软件工程师,软件架构师,以及喜欢写代码的技术经理准备的。如果你需要对所从事系统的架构做出决策 —— 例如你需要选择解决某个特定问题的工具,并找出如何最好地使用这些工具,那么这本书对你尤有价值。但即使你无法选择你的工具,本书仍将帮助你更好地了解所使用工具的长处和短处。 -你应当具有一些开发Web应用或网络服务的经验,且应当熟悉关系型数据库和SQL。任何你了解的非关系型数据库和其他与数据相关工具都会有所帮助,但不是必需的。对常见网络协议如TCP和HTTP的大概理解是有帮助的。编程语言或框架的选择对阅读本书没有任何不同影响。 +你应当具有一些开发 Web 应用或网络服务的经验,且应当熟悉关系型数据库和 SQL。任何你了解的非关系型数据库和其他与数据相关工具都会有所帮助,但不是必需的。对常见网络协议如 TCP 和 HTTP 的大概理解是有帮助的。编程语言或框架的选择对阅读本书没有任何不同影响。 如果以下任意一条对你为真,你会发现这本书很有价值: -* 你想了解如何使数据系统可伸缩,例如,支持拥有数百万用户的Web或移动应用。 +* 你想了解如何使数据系统可伸缩,例如,支持拥有数百万用户的 Web 或移动应用。 * 你需要提高应用程序的可用性(最大限度地减少停机时间),保持稳定运行。 * 你正在寻找使系统在长期运行过程易于维护的方法,即使系统规模增长,需求与技术也发生变化。 * 你对事物的运作方式有着天然的好奇心,并且希望知道一些主流网站和在线服务背后发生的事情。这本书打破了各种数据库和数据处理系统的内幕,探索这些系统设计中的智慧是非常有趣的。 @@ -48,7 +48,7 @@ 本书并不会尝试告诉读者如何安装或使用特定的软件包或API,因为已经有大量文档给出了详细的使用说明。相反,我们会讨论数据系统的基石——各种原则与利弊权衡,并探讨了不同产品所做出的不同设计决策。 -在电子书中包含了在线资源全文的链接。所有链接在出版时都进行了验证,但不幸的是,由于网络的自然规律,链接往往会频繁地破损。如果你遇到链接断开的情况,或者正在阅读本书的打印副本,可以使用搜索引擎查找参考文献。对于学术论文,你可以在Google学术中搜索标题,查找可以公开获取的PDF文件。或者,你也可以在 https://github.com/ept/ddia-references 中找到所有的参考资料,我们在那儿维护最新的链接。 +在电子书中包含了在线资源全文的链接。所有链接在出版时都进行了验证,但不幸的是,由于网络的自然规律,链接往往会频繁地破损。如果你遇到链接断开的情况,或者正在阅读本书的打印副本,可以使用搜索引擎查找参考文献。对于学术论文,你可以在 Google 学术中搜索标题,查找可以公开获取的 PDF 文件。或者,你也可以在 https://github.com/ept/ddia-references 中找到所有的参考资料,我们在那儿维护最新的链接。 我们主要关注的是数据系统的**架构(architecture)**,以及它们被集成到数据密集型应用中的方式。本书没有足够的空间覆盖部署、运维、安全、管理等领域 —— 这些都是复杂而重要的主题,仅仅在本书中用粗略的注解讨论这些对它们很不公平。每个领域都值得用单独的书去讲。 @@ -89,8 +89,8 @@ For more information, please visit http://oreilly.com/safari. 更多人通过审阅草稿并提供反馈意见在本书的创作过程中做出了无价的贡献。我要特别感谢Raul Agepati, Tyler Akidau, Mattias Andersson, Sasha Baranov, Veena Basavaraj, David Beyer, Jim Brikman, Paul Carey, Raul Castro Fernandez, Joseph Chow, Derek Elkins, Sam Elliott, Alexander Gallego, Mark Grover, Stu Halloway, Heidi Howard, Nicola Kleppmann, Stefan Kruppa, Bjorn Madsen, Sander Mak, Stefan Podkowinski, Phil Potter, Hamid Ramazani, Sam Stokes, 以及Ben Summers。当然对于本书中的任何遗留错误或难以接受的见解,我都承担全部责任。 -为了帮助这本书落地,并且耐心地处理我缓慢的写作和不寻常的要求,我要对编辑Marie Beaugureau,Mike Loukides,Ann Spencer和O'Reilly的所有团队表示感谢。我要感谢Rachel Head帮我找到了合适的术语。我要感谢Alastair Beresford,Susan Goodhue,Neha Narkhede和Kevin Scott,在其他工作事务之外给了我充分地创作时间和自由。 +为了帮助这本书落地,并且耐心地处理我缓慢的写作和不寻常的要求,我要对编辑 Marie Beaugureau,Mike Loukides,Ann Spencer 和 O'Reilly 的所有团队表示感谢。我要感谢 Rachel Head 帮我找到了合适的术语。我要感谢 Alastair Beresford,Susan Goodhue,Neha Narkhede 和 Kevin Scott,在其他工作事务之外给了我充分地创作时间和自由。 -特别感谢Shabbir Diwan和Edie Freedman,他们非常用心地为各章配了地图。他们提出了不落俗套的灵感,创作了这些地图,美丽而引人入胜,真是太棒了。 +特别感谢 Shabbir Diwan 和 Edie Freedman,他们非常用心地为各章配了地图。他们提出了不落俗套的灵感,创作了这些地图,美丽而引人入胜,真是太棒了。 最后我要表达对家人和朋友们的爱,没有他们,我将无法走完这个将近四年的写作历程。你们是最棒的。 diff --git a/zh-tw/README.md b/zh-tw/README.md index ac5a142e..dfdffee4 100644 --- a/zh-tw/README.md +++ b/zh-tw/README.md @@ -9,7 +9,7 @@ > 使用 [Typora](https://www.typora.io)、[Gitbook](https://vonng.gitbook.io/vonng/) 或 [Github Pages](https://vonng.github.io/ddia) 以獲取最佳閱讀體驗。 > -> 本地:你可在專案根目錄中執行`make`,並透過瀏覽器閱讀([線上預覽](http://ddia.vonng.com/#/))。 +> 本地:你可在專案根目錄中執行 `make`,並透過瀏覽器閱讀([線上預覽](http://ddia.vonng.com/#/))。 ## 譯序 @@ -25,7 +25,7 @@ 本書為資料系統的設計、實現、與評價提供了很好的概念框架。讀完並理解本書內容後,讀者可以輕鬆看破大多數的技術忽悠,與技術磚家撕起來虎虎生風🤣。 -這是2017年譯者讀過最好的一本技術類書籍,這麼好的書沒有中文翻譯,實在是遺憾。某不才,願為先進技術文化的傳播貢獻一份力量。既可以深入學習有趣的技術主題,又可以鍛鍊中英文語言文字功底,何樂而不為? +這是 2017 年譯者讀過最好的一本技術類書籍,這麼好的書沒有中文翻譯,實在是遺憾。某不才,願為先進技術文化的傳播貢獻一份力量。既可以深入學習有趣的技術主題,又可以鍛鍊中英文語言文字功底,何樂而不為? ## 前言 @@ -36,7 +36,7 @@ > 計算是一種流行文化,流行文化鄙視歷史。 流行文化關乎個體身份和參與感,但與合作無關。流行文化活在當下,也與過去和未來無關。 我認為大部分(為了錢)編寫程式碼的人就是這樣的, 他們不知道自己的文化來自哪裡。 > -> ——阿蘭·凱接受Dobb博士的雜誌採訪時(2012年) +> —— 阿蘭・凱接受 Dobb 博士的雜誌採訪時(2012 年) ## 目錄 @@ -127,7 +127,7 @@ 從原作者處得知,已經有簡體中文的翻譯計劃,將於2018年末完成。[購買地址](https://search.jd.com/Search?keyword=設計資料密集型應用) -譯者純粹出於**學習目的**與**個人興趣**翻譯本書,不追求任何經濟利益。 +譯者純粹出於 **學習目的** 與 **個人興趣** 翻譯本書,不追求任何經濟利益。 譯者保留對此版本譯文的署名權,其他權利以原作者和出版社的主張為準。 diff --git a/zh-tw/ch10.md b/zh-tw/ch10.md index 92743553..1516b666 100644 --- a/zh-tw/ch10.md +++ b/zh-tw/ch10.md @@ -222,7 +222,7 @@ MapReduce是一個程式設計框架,你可以使用它編寫程式碼來處 這四個步驟可以作為一個MapReduce作業執行。步驟2(Map)和4(Reduce)是你編寫自定義資料處理程式碼的地方。步驟1(將檔案分解成記錄)由輸入格式解析器處理。步驟3中的排序步驟隱含在MapReduce中 —— 你不必編寫它,因為Mapper的輸出始終在送往Reducer之前進行排序。 -要建立MapReduce作業,你需要實現兩個回撥函式,Mapper和Reducer,其行為如下(請參閱“[MapReduce查詢](ch2.md#MapReduce查詢)”): +要建立MapReduce作業,你需要實現兩個回撥函式,Mapper和Reducer,其行為如下(請參閱“[MapReduce 查詢](ch2.md#MapReduce查詢)”): * Mapper @@ -238,7 +238,7 @@ MapReduce是一個程式設計框架,你可以使用它編寫程式碼來處 MapReduce與Unix命令管道的主要區別在於,MapReduce可以在多臺機器上並行執行計算,而無需編寫程式碼來顯式處理並行問題。Mapper和Reducer一次只能處理一條記錄;它們不需要知道它們的輸入來自哪裡,或者輸出去往什麼地方,所以框架可以處理在機器之間移動資料的複雜性。 -在分散式計算中可以使用標準的Unix工具作為Mapper和Reducer【25】,但更常見的是,它們被實現為傳統程式語言的函式。在Hadoop MapReduce中,Mapper和Reducer都是實現特定介面的Java類。在MongoDB和CouchDB中,Mapper和Reducer都是JavaScript函式(請參閱“[MapReduce查詢](ch2.md#MapReduce查詢)”)。 +在分散式計算中可以使用標準的Unix工具作為Mapper和Reducer【25】,但更常見的是,它們被實現為傳統程式語言的函式。在Hadoop MapReduce中,Mapper和Reducer都是實現特定介面的Java類。在MongoDB和CouchDB中,Mapper和Reducer都是JavaScript函式(請參閱“[MapReduce 查詢](ch2.md#MapReduce查詢)”)。 [圖10-1](../img/fig10-1.png)顯示了Hadoop MapReduce作業中的資料流。其並行化基於分割槽(請參閱[第六章](ch6.md)):作業的輸入通常是HDFS中的一個目錄,輸入目錄中的每個檔案或檔案塊都被認為是一個單獨的分割槽,可以單獨處理map任務([圖10-1](../img/fig10-1.png)中的m1,m2和m3標記)。 diff --git a/zh-tw/ch2.md b/zh-tw/ch2.md index 14a4f550..ef63a110 100644 --- a/zh-tw/ch2.md +++ b/zh-tw/ch2.md @@ -4,79 +4,79 @@ > 語言的邊界就是思想的邊界。 > -> —— 路德維奇·維特根斯坦,《邏輯哲學》(1922) +> —— 路德維奇・維特根斯坦,《邏輯哲學》(1922) > ------------------- [TOC] -資料模型可能是軟體開發中最重要的部分了,因為它們的影響如此深遠:不僅僅影響著軟體的編寫方式,而且影響著我們的**解題思路**。 +資料模型可能是軟體開發中最重要的部分了,因為它們的影響如此深遠:不僅僅影響著軟體的編寫方式,而且影響著我們的 **解題思路**。 -多數應用使用層層疊加的資料模型構建。對於每層資料模型的關鍵問題是:它是如何用低一層資料模型來**表示**的?例如: +多數應用使用層層疊加的資料模型構建。對於每層資料模型的關鍵問題是:它是如何用低一層資料模型來 **表示** 的?例如: -1. 作為一名應用開發人員,你觀察現實世界(裡面有人員、組織、貨物、行為、資金流向、感測器等),並採用物件或資料結構,以及操控那些資料結構的API來進行建模。那些結構通常是特定於應用程式的。 -2. 當要儲存那些資料結構時,你可以利用通用資料模型來表示它們,如JSON或XML文件、關係資料庫中的表或圖模型。 -3. 資料庫軟體的工程師選定如何以記憶體、磁碟或網路上的位元組來表示JSON/XML/關係/圖資料。這類表示形式使資料有可能以各種方式來查詢,搜尋,操縱和處理。 +1. 作為一名應用開發人員,你觀察現實世界(裡面有人員、組織、貨物、行為、資金流向、感測器等),並採用物件或資料結構,以及操控那些資料結構的 API 來進行建模。那些結構通常是特定於應用程式的。 +2. 當要儲存那些資料結構時,你可以利用通用資料模型來表示它們,如 JSON 或 XML 文件、關係資料庫中的表或圖模型。 +3. 資料庫軟體的工程師選定如何以記憶體、磁碟或網路上的位元組來表示 JSON / XML/ 關係 / 圖資料。這類表示形式使資料有可能以各種方式來查詢,搜尋,操縱和處理。 4. 在更低的層次上,硬體工程師已經想出了使用電流、光脈衝、磁場或者其他東西來表示位元組的方法。 -一個複雜的應用程式可能會有更多的中間層次,比如基於API的API,不過基本思想仍然是一樣的:每個層都透過提供一個明確的資料模型來隱藏更低層次中的複雜性。這些抽象允許不同的人群有效地協作(例如資料庫廠商的工程師和使用資料庫的應用程式開發人員)。 +一個複雜的應用程式可能會有更多的中間層次,比如基於 API 的 API,不過基本思想仍然是一樣的:每個層都透過提供一個明確的資料模型來隱藏更低層次中的複雜性。這些抽象允許不同的人群有效地協作(例如資料庫廠商的工程師和使用資料庫的應用程式開發人員)。 資料模型種類繁多,每個資料模型都帶有如何使用的設想。有些用法很容易,有些則不支援如此;有些操作執行很快,有些則表現很差;有些資料轉換非常自然,有些則很麻煩。 掌握一個數據模型需要花費很多精力(想想關係資料建模有多少本書)。即便只使用一個數據模型,不用操心其內部工作機制,構建軟體也是非常困難的。然而,因為資料模型對上層軟體的功能(能做什麼,不能做什麼)有著至深的影響,所以選擇一個適合的資料模型是非常重要的。 -在本章中,我們將研究一系列用於資料儲存和查詢的通用資料模型(前面列表中的第2點)。特別地,我們將比較關係模型,文件模型和少量基於圖形的資料模型。我們還將檢視各種查詢語言並比較它們的用例。在[第三章](ch3.md)中,我們將討論儲存引擎是如何工作的。也就是說,這些資料模型實際上是如何實現的(列表中的第3點)。 +在本章中,我們將研究一系列用於資料儲存和查詢的通用資料模型(前面列表中的第 2 點)。特別地,我們將比較關係模型,文件模型和少量基於圖形的資料模型。我們還將檢視各種查詢語言並比較它們的用例。在 [第三章](ch3.md) 中,我們將討論儲存引擎是如何工作的。也就是說,這些資料模型實際上是如何實現的(列表中的第 3 點)。 ## 關係模型與文件模型 -現在最著名的資料模型可能是SQL。它基於Edgar Codd在1970年提出的關係模型【1】:資料被組織成**關係**(SQL中稱作**表**),其中每個關係是**元組**(SQL中稱作**行**)的無序集合。 +現在最著名的資料模型可能是 SQL。它基於 Edgar Codd 在 1970 年提出的關係模型【1】:資料被組織成 **關係**(SQL 中稱作 **表**),其中每個關係是 **元組**(SQL 中稱作 **行**) 的無序集合。 -關係模型曾是一個理論性的提議,當時很多人都懷疑是否能夠有效實現它。然而到了20世紀80年代中期,關係資料庫管理系統(RDBMSes)和SQL已成為大多數人們儲存和查詢某些常規結構的資料的首選工具。關係資料庫已經持續稱霸了大約25~30年——這對計算機史來說是極其漫長的時間。 +關係模型曾是一個理論性的提議,當時很多人都懷疑是否能夠有效實現它。然而到了 20 世紀 80 年代中期,關係資料庫管理系統(RDBMSes)和 SQL 已成為大多數人們儲存和查詢某些常規結構的資料的首選工具。關係資料庫已經持續稱霸了大約 25~30 年 —— 這對計算機史來說是極其漫長的時間。 -關係資料庫起源於商業資料處理,在20世紀60年代和70年代用大型計算機來執行。從今天的角度來看,那些用例顯得很平常:典型的**事務處理**(將銷售或銀行交易,航空公司預訂,庫存管理資訊記錄在庫)和**批處理**(客戶發票,工資單,報告)。 +關係資料庫起源於商業資料處理,在 20 世紀 60 年代和 70 年代用大型計算機來執行。從今天的角度來看,那些用例顯得很平常:典型的 **事務處理**(將銷售或銀行交易,航空公司預訂,庫存管理資訊記錄在庫)和 **批處理**(客戶發票,工資單,報告)。 當時的其他資料庫迫使應用程式開發人員必須考慮資料庫內部的資料表示形式。關係模型致力於將上述實現細節隱藏在更簡潔的介面之後。 -多年來,在資料儲存和查詢方面存在著許多相互競爭的方法。在20世紀70年代和80年代初,網狀模型(network model)和層次模型(hierarchical model)曾是主要的選擇,但關係模型(relational model)隨後佔據了主導地位。物件資料庫在20世紀80年代末和90年代初來了又去。XML資料庫在二十一世紀初出現,但只有小眾採用過。關係模型的每個競爭者都在其時代產生了大量的炒作,但從來沒有持續【2】。 +多年來,在資料儲存和查詢方面存在著許多相互競爭的方法。在 20 世紀 70 年代和 80 年代初,網狀模型(network model)和層次模型(hierarchical model)曾是主要的選擇,但關係模型(relational model)隨後佔據了主導地位。物件資料庫在 20 世紀 80 年代末和 90 年代初來了又去。XML 資料庫在二十一世紀初出現,但只有小眾採用過。關係模型的每個競爭者都在其時代產生了大量的炒作,但從來沒有持續【2】。 隨著電腦越來越強大和互聯,它們開始用於日益多樣化的目的。關係資料庫非常成功地被推廣到業務資料處理的原始範圍之外更為廣泛的用例上。你今天在網上看到的大部分內容依舊是由關係資料庫來提供支援,無論是線上釋出,討論,社交網路,電子商務,遊戲,軟體即服務生產力應用程式等等內容。 -### NoSQL的誕生 +### NoSQL 的誕生 -現在 - 2010年代,NoSQL開始了最新一輪嘗試,試圖推翻關係模型的統治地位。“NoSQL”這個名字讓人遺憾,因為實際上它並沒有涉及到任何特定的技術。最初它只是作為一個醒目的Twitter標籤,用在2009年一個關於分散式,非關係資料庫上的開源聚會上。無論如何,這個術語觸動了某些神經,並迅速在網路創業社群內外傳播開來。好些有趣的資料庫系統現在都與 *#NoSQL* 標籤相關聯,並且NoSQL被追溯性地重新解釋為**不僅是SQL(Not Only SQL)** 【4】。 +現在 - 2010 年代,NoSQL 開始了最新一輪嘗試,試圖推翻關係模型的統治地位。“NoSQL” 這個名字讓人遺憾,因為實際上它並沒有涉及到任何特定的技術。最初它只是作為一個醒目的 Twitter 標籤,用在 2009 年一個關於分散式,非關係資料庫上的開源聚會上。無論如何,這個術語觸動了某些神經,並迅速在網路創業社群內外傳播開來。好些有趣的資料庫系統現在都與 *#NoSQL* 標籤相關聯,並且 NoSQL 被追溯性地重新解釋為 **不僅是 SQL(Not Only SQL)** 【4】。 -採用NoSQL資料庫的背後有幾個驅動因素,其中包括: +採用 NoSQL 資料庫的背後有幾個驅動因素,其中包括: * 需要比關係資料庫更好的可伸縮性,包括非常大的資料集或非常高的寫入吞吐量 * 相比商業資料庫產品,免費和開源軟體更受偏愛 * 關係模型不能很好地支援一些特殊的查詢操作 * 受挫於關係模型的限制性,渴望一種更具多動態性與表現力的資料模型【5】 -不同的應用程式有不同的需求,一個用例的最佳技術選擇可能不同於另一個用例的最佳技術選擇。因此,在可預見的未來,關係資料庫似乎可能會繼續與各種非關係資料庫一起使用 - 這種想法有時也被稱為**混合持久化(polyglot persistence)**。 +不同的應用程式有不同的需求,一個用例的最佳技術選擇可能不同於另一個用例的最佳技術選擇。因此,在可預見的未來,關係資料庫似乎可能會繼續與各種非關係資料庫一起使用 - 這種想法有時也被稱為 **混合持久化(polyglot persistence)**。 ### 物件關係不匹配 -目前大多數應用程式開發都使用面向物件的程式語言來開發,這導致了對SQL資料模型的普遍批評:如果資料儲存在關係表中,那麼需要一個笨拙的轉換層,處於應用程式程式碼中的物件和表,行,列的資料庫模型之間。模型之間的不連貫有時被稱為**阻抗不匹配(impedance mismatch)**[^i]。 +目前大多數應用程式開發都使用面向物件的程式語言來開發,這導致了對 SQL 資料模型的普遍批評:如果資料儲存在關係表中,那麼需要一個笨拙的轉換層,處於應用程式程式碼中的物件和表,行,列的資料庫模型之間。模型之間的不連貫有時被稱為 **阻抗不匹配(impedance mismatch)**[^i]。 [^i]: 一個從電子學借用的術語。每個電路的輸入和輸出都有一定的阻抗(交流電阻)。當你將一個電路的輸出連線到另一個電路的輸入時,如果兩個電路的輸出和輸入阻抗匹配,則連線上的功率傳輸將被最大化。阻抗不匹配會導致訊號反射及其他問題。 -像ActiveRecord和Hibernate這樣的 **物件關係對映(ORM object-relational mapping)** 框架可以減少這個轉換層所需的樣板程式碼的數量,但是它們不能完全隱藏這兩個模型之間的差異。 +像 ActiveRecord 和 Hibernate 這樣的 **物件關係對映(ORM object-relational mapping)** 框架可以減少這個轉換層所需的樣板程式碼的數量,但是它們不能完全隱藏這兩個模型之間的差異。 ![](../img/fig2-1.png) -**圖2-1 使用關係型模式來表示領英簡介** +**圖 2-1 使用關係型模式來表示領英簡介** -例如,[圖2-1](../img/fig2-1.png)展示瞭如何在關係模式中表示簡歷(一個LinkedIn簡介)。整個簡介可以透過一個唯一的識別符號`user_id`來標識。像`first_name`和`last_name`這樣的欄位每個使用者只出現一次,所以可以在User表上將其建模為列。但是,大多數人在職業生涯中擁有多於一份的工作,人們可能有不同樣的教育階段和任意數量的聯絡資訊。從使用者到這些專案之間存在一對多的關係,可以用多種方式來表示: +例如,[圖 2-1](../img/fig2-1.png) 展示瞭如何在關係模式中表示簡歷(一個 LinkedIn 簡介)。整個簡介可以透過一個唯一的識別符號 `user_id` 來標識。像 `first_name` 和 `last_name` 這樣的欄位每個使用者只出現一次,所以可以在 User 表上將其建模為列。但是,大多數人在職業生涯中擁有多於一份的工作,人們可能有不同樣的教育階段和任意數量的聯絡資訊。從使用者到這些專案之間存在一對多的關係,可以用多種方式來表示: -* 傳統SQL模型(SQL:1999之前)中,最常見的規範化表示形式是將職位,教育和聯絡資訊放在單獨的表中,對User表提供外來鍵引用,如[圖2-1](../img/fig2-1.png)所示。 -* 後續的SQL標準增加了對結構化資料型別和XML資料的支援;這允許將多值資料儲存在單行內,並支援在這些文件內查詢和索引。這些功能在Oracle,IBM DB2,MS SQL Server和PostgreSQL中都有不同程度的支援【6,7】。JSON資料型別也得到多個數據庫的支援,包括IBM DB2,MySQL和PostgreSQL 【8】。 -* 第三種選擇是將職業,教育和聯絡資訊編碼為JSON或XML文件,將其儲存在資料庫的文字列中,並讓應用程式解析其結構和內容。這種配置下,通常不能使用資料庫來查詢該編碼列中的值。 +* 傳統 SQL 模型(SQL:1999 之前)中,最常見的規範化表示形式是將職位,教育和聯絡資訊放在單獨的表中,對 User 表提供外來鍵引用,如 [圖 2-1](../img/fig2-1.png) 所示。 +* 後續的 SQL 標準增加了對結構化資料型別和 XML 資料的支援;這允許將多值資料儲存在單行內,並支援在這些文件內查詢和索引。這些功能在 Oracle,IBM DB2,MS SQL Server 和 PostgreSQL 中都有不同程度的支援【6,7】。JSON 資料型別也得到多個數據庫的支援,包括 IBM DB2,MySQL 和 PostgreSQL 【8】。 +* 第三種選擇是將職業,教育和聯絡資訊編碼為 JSON 或 XML 文件,將其儲存在資料庫的文字列中,並讓應用程式解析其結構和內容。這種配置下,通常不能使用資料庫來查詢該編碼列中的值。 -對於一個像簡歷這樣自包含文件的資料結構而言,JSON表示是非常合適的:請參閱[例2-1]()。JSON比XML更簡單。面向文件的資料庫(如MongoDB 【9】,RethinkDB 【10】,CouchDB 【11】和Espresso【12】)支援這種資料模型。 +對於一個像簡歷這樣自包含文件的資料結構而言,JSON 表示是非常合適的:請參閱 [例 2-1]()。JSON 比 XML 更簡單。面向文件的資料庫(如 MongoDB 【9】,RethinkDB 【10】,CouchDB 【11】和 Espresso【12】)支援這種資料模型。 -**例2-1. 用JSON文件表示一個LinkedIn簡介** +**例 2-1. 用 JSON 文件表示一個 LinkedIn 簡介** ```json { @@ -116,39 +116,39 @@ } ``` -有一些開發人員認為JSON模型減少了應用程式程式碼和儲存層之間的阻抗不匹配。不過,正如我們將在[第四章](ch4.md)中看到的那樣,JSON作為資料編碼格式也存在問題。缺乏一個模式往往被認為是一個優勢;我們將在“[文件模型中的模式靈活性](#文件模型中的模式靈活性)”中討論這個問題。 +有一些開發人員認為 JSON 模型減少了應用程式程式碼和儲存層之間的阻抗不匹配。不過,正如我們將在 [第四章](ch4.md) 中看到的那樣,JSON 作為資料編碼格式也存在問題。缺乏一個模式往往被認為是一個優勢;我們將在 “[文件模型中的模式靈活性](#文件模型中的模式靈活性)” 中討論這個問題。 -JSON表示比[圖2-1](../img/fig2-1.png)中的多表模式具有更好的**區域性性(locality)**。如果在前面的關係型示例中獲取簡介,那需要執行多個查詢(透過`user_id`查詢每個表),或者在User表與其下屬表之間混亂地執行多路連線。而在JSON表示中,所有相關資訊都在同一個地方,一個查詢就足夠了。 +JSON 表示比 [圖 2-1](../img/fig2-1.png) 中的多表模式具有更好的 **區域性性(locality)**。如果在前面的關係型示例中獲取簡介,那需要執行多個查詢(透過 `user_id` 查詢每個表),或者在 User 表與其下屬表之間混亂地執行多路連線。而在 JSON 表示中,所有相關資訊都在同一個地方,一個查詢就足夠了。 -從使用者簡介檔案到使用者職位,教育歷史和聯絡資訊,這種一對多關係隱含了資料中的一個樹狀結構,而JSON表示使得這個樹狀結構變得明確(見[圖2-2](../img/fig2-2.png))。 +從使用者簡介檔案到使用者職位,教育歷史和聯絡資訊,這種一對多關係隱含了資料中的一個樹狀結構,而 JSON 表示使得這個樹狀結構變得明確(見 [圖 2-2](../img/fig2-2.png))。 ![](../img/fig2-2.png) -**圖2-2 一對多關係構建了一個樹結構** +**圖 2-2 一對多關係構建了一個樹結構** ### 多對一和多對多的關係 -在上一節的[例2-1]()中,`region_id`和`industry_id`是以ID,而不是純字串“Greater Seattle Area”和“Philanthropy”的形式給出的。為什麼? +在上一節的 [例 2-1]() 中,`region_id` 和 `industry_id` 是以 ID,而不是純字串 “Greater Seattle Area” 和 “Philanthropy” 的形式給出的。為什麼? 如果使用者介面用一個自由文字欄位來輸入區域和行業,那麼將他們儲存為純文字字串是合理的。另一方式是給出地理區域和行業的標準化的列表,並讓使用者從下拉列表或自動填充器中進行選擇,其優勢如下: * 各個簡介之間樣式和拼寫統一 * 避免歧義(例如,如果有幾個同名的城市) -* 易於更新——名稱只儲存在一個地方,如果需要更改(例如,由於政治事件而改變城市名稱),很容易進行全面更新。 -* 本地化支援——當網站翻譯成其他語言時,標準化的列表可以被本地化,使得地區和行業可以使用使用者的語言來顯示 -* 更好的搜尋——例如,搜尋華盛頓州的慈善家就會匹配這份簡介,因為地區列表可以編碼記錄西雅圖在華盛頓這一事實(從“Greater Seattle Area”這個字串中看不出來) +* 易於更新 —— 名稱只儲存在一個地方,如果需要更改(例如,由於政治事件而改變城市名稱),很容易進行全面更新。 +* 本地化支援 —— 當網站翻譯成其他語言時,標準化的列表可以被本地化,使得地區和行業可以使用使用者的語言來顯示 +* 更好的搜尋 —— 例如,搜尋華盛頓州的慈善家就會匹配這份簡介,因為地區列表可以編碼記錄西雅圖在華盛頓這一事實(從 “Greater Seattle Area” 這個字串中看不出來) -儲存ID還是文字字串,這是個 **副本(duplication)** 問題。當使用ID時,對人類有意義的資訊(比如單詞:Philanthropy)只儲存在一處,所有引用它的地方使用ID(ID只在資料庫中有意義)。當直接儲存文字時,對人類有意義的資訊會複製在每處使用記錄中。 +儲存 ID 還是文字字串,這是個 **副本(duplication)** 問題。當使用 ID 時,對人類有意義的資訊(比如單詞:Philanthropy)只儲存在一處,所有引用它的地方使用 ID(ID 只在資料庫中有意義)。當直接儲存文字時,對人類有意義的資訊會複製在每處使用記錄中。 -使用ID的好處是,ID對人類沒有任何意義,因而永遠不需要改變:ID可以保持不變,即使它標識的資訊發生變化。任何對人類有意義的東西都可能需要在將來某個時候改變——如果這些資訊被複制,所有的冗餘副本都需要更新。這會導致寫入開銷,也存在不一致的風險(一些副本被更新了,還有些副本沒有被更新)。去除此類重複是資料庫 **規範化(normalization)** 的關鍵思想。[^ii] +使用 ID 的好處是,ID 對人類沒有任何意義,因而永遠不需要改變:ID 可以保持不變,即使它標識的資訊發生變化。任何對人類有意義的東西都可能需要在將來某個時候改變 —— 如果這些資訊被複制,所有的冗餘副本都需要更新。這會導致寫入開銷,也存在不一致的風險(一些副本被更新了,還有些副本沒有被更新)。去除此類重複是資料庫 **規範化(normalization)** 的關鍵思想。[^ii] -[^ii]: 關於關係模型的文獻區分了幾種不同的規範形式,但這些區別幾乎沒有實際意義。一個經驗法則是,如果重複儲存了可以儲存在一個地方的值,則模式就不是**規範化(normalized)** 的。 +[^ii]: 關於關係模型的文獻區分了幾種不同的規範形式,但這些區別幾乎沒有實際意義。一個經驗法則是,如果重複儲存了可以儲存在一個地方的值,則模式就不是 **規範化(normalized)** 的。 -> 資料庫管理員和開發人員喜歡爭論規範化和非規範化,讓我們暫時保留判斷吧。在本書的[第三部分](part-iii.md),我們將回到這個話題,探討系統的方法用以處理快取,非規範化和衍生資料。 +> 資料庫管理員和開發人員喜歡爭論規範化和非規範化,讓我們暫時保留判斷吧。在本書的 [第三部分](part-iii.md),我們將回到這個話題,探討系統的方法用以處理快取,非規範化和衍生資料。 -不幸的是,對這些資料進行規範化需要多對一的關係(許多人生活在一個特定的地區,許多人在一個特定的行業工作),這與文件模型不太吻合。在關係資料庫中,透過ID來引用其他表中的行是正常的,因為連線很容易。在文件資料庫中,一對多樹結構沒有必要用連線,對連線的支援通常很弱[^iii]。 +不幸的是,對這些資料進行規範化需要多對一的關係(許多人生活在一個特定的地區,許多人在一個特定的行業工作),這與文件模型不太吻合。在關係資料庫中,透過 ID 來引用其他表中的行是正常的,因為連線很容易。在文件資料庫中,一對多樹結構沒有必要用連線,對連線的支援通常很弱 [^iii]。 -[^iii]: 在撰寫本文時,RethinkDB支援連線,MongoDB不支援連線,而CouchDB只支援預先宣告的檢視。 +[^iii]: 在撰寫本文時,RethinkDB 支援連線,MongoDB 不支援連線,而 CouchDB 只支援預先宣告的檢視。 如果資料庫本身不支援連線,則必須在應用程式程式碼中透過對資料庫進行多個查詢來模擬連線。(在這種情況中,地區和行業的列表可能很小,改動很少,應用程式可以簡單地將其儲存在記憶體中。不過,執行連線的工作從資料庫被轉移到應用程式程式碼上。 @@ -156,7 +156,7 @@ JSON表示比[圖2-1](../img/fig2-1.png)中的多表模式具有更好的**區 * 組織和學校作為實體 - 在前面的描述中,`organization`(使用者工作的公司)和`school_name`(他們學習的地方)只是字串。也許他們應該是對實體的引用呢?然後,每個組織、學校或大學都可以擁有自己的網頁(標識,新聞提要等)。每個簡歷可以連結到它所提到的組織和學校,並且包括他們的圖示和其他資訊(請參閱[圖2-3](../img/fig2-3.png),來自LinkedIn的一個例子)。 + 在前面的描述中,`organization`(使用者工作的公司)和 `school_name`(他們學習的地方)只是字串。也許他們應該是對實體的引用呢?然後,每個組織、學校或大學都可以擁有自己的網頁(標識,新聞提要等)。每個簡歷可以連結到它所提到的組織和學校,並且包括他們的圖示和其他資訊(請參閱 [圖 2-3](../img/fig2-3.png),來自 LinkedIn 的一個例子)。 * 推薦 @@ -164,83 +164,83 @@ JSON表示比[圖2-1](../img/fig2-1.png)中的多表模式具有更好的**區 ![](../img/fig2-3.png) -**圖2-3 公司名不僅是字串,還是一個指向公司實體的連結(LinkedIn截圖)** +**圖 2-3 公司名不僅是字串,還是一個指向公司實體的連結(LinkedIn 截圖)** -[圖2-4](../img/fig2-4.png)闡明瞭這些新功能需要如何使用多對多關係。每個虛線矩形內的資料可以分組成一個文件,但是對單位,學校和其他使用者的引用需要表示成引用,並且在查詢時需要連線。 +[圖 2-4](../img/fig2-4.png) 闡明瞭這些新功能需要如何使用多對多關係。每個虛線矩形內的資料可以分組成一個文件,但是對單位,學校和其他使用者的引用需要表示成引用,並且在查詢時需要連線。 ![](../img/fig2-4.png) -**圖2-4 使用多對多關係擴充套件簡歷** +**圖 2-4 使用多對多關係擴充套件簡歷** ### 文件資料庫是否在重蹈覆轍? -在多對多的關係和連線已常規用在關係資料庫時,文件資料庫和NoSQL重啟了辯論:如何以最佳方式在資料庫中表示多對多關係。那場辯論可比NoSQL古老得多,事實上,最早可以追溯到計算機化資料庫系統。 +在多對多的關係和連線已常規用在關係資料庫時,文件資料庫和 NoSQL 重啟了辯論:如何以最佳方式在資料庫中表示多對多關係。那場辯論可比 NoSQL 古老得多,事實上,最早可以追溯到計算機化資料庫系統。 -20世紀70年代最受歡迎的業務資料處理資料庫是IBM的資訊管理系統(IMS),最初是為了阿波羅太空計劃的庫存管理而開發的,並於1968年有了首次商業釋出【13】。目前它仍在使用和維護,執行在IBM大型機的OS/390上【14】。 +20 世紀 70 年代最受歡迎的業務資料處理資料庫是 IBM 的資訊管理系統(IMS),最初是為了阿波羅太空計劃的庫存管理而開發的,並於 1968 年有了首次商業釋出【13】。目前它仍在使用和維護,執行在 IBM 大型機的 OS/390 上【14】。 -IMS的設計中使用了一個相當簡單的資料模型,稱為**層次模型(hierarchical model)**,它與文件資料庫使用的JSON模型有一些驚人的相似之處【2】。它將所有資料表示為巢狀在記錄中的記錄樹,這很像[圖2-2](../img/fig2-2.png)的JSON結構。 +IMS 的設計中使用了一個相當簡單的資料模型,稱為 **層次模型(hierarchical model)**,它與文件資料庫使用的 JSON 模型有一些驚人的相似之處【2】。它將所有資料表示為巢狀在記錄中的記錄樹,這很像 [圖 2-2](../img/fig2-2.png) 的 JSON 結構。 -同文檔資料庫一樣,IMS能良好處理一對多的關係,但是很難應對多對多的關係,並且不支援連線。開發人員必須決定是否複製(非規範化)資料或手動解決從一個記錄到另一個記錄的引用。這些二十世紀六七十年代的問題與現在開發人員遇到的文件資料庫問題非常相似【15】。 +同文檔資料庫一樣,IMS 能良好處理一對多的關係,但是很難應對多對多的關係,並且不支援連線。開發人員必須決定是否複製(非規範化)資料或手動解決從一個記錄到另一個記錄的引用。這些二十世紀六七十年代的問題與現在開發人員遇到的文件資料庫問題非常相似【15】。 -那時人們提出了各種不同的解決方案來解決層次模型的侷限性。其中最突出的兩個是**關係模型**(relational model,它變成了SQL,並統治了世界)和**網狀模型**(network model,最初很受關注,但最終變得冷門)。這兩個陣營之間的“大辯論”在70年代持續了很久時間【2】。 +那時人們提出了各種不同的解決方案來解決層次模型的侷限性。其中最突出的兩個是 **關係模型**(relational model,它變成了 SQL,並統治了世界)和 **網狀模型**(network model,最初很受關注,但最終變得冷門)。這兩個陣營之間的 “大辯論” 在 70 年代持續了很久時間【2】。 那兩個模式解決的問題與當前的問題相關,因此值得簡要回顧一下那場辯論。 #### 網狀模型 -網狀模型由一個稱為資料系統語言會議(CODASYL)的委員會進行了標準化,並被數個不同的資料庫廠商實現;它也被稱為CODASYL模型【16】。 +網狀模型由一個稱為資料系統語言會議(CODASYL)的委員會進行了標準化,並被數個不同的資料庫廠商實現;它也被稱為 CODASYL 模型【16】。 -CODASYL模型是層次模型的推廣。在層次模型的樹結構中,每條記錄只有一個父節點;在網路模式中,每條記錄可能有多個父節點。例如,“Greater Seattle Area”地區可能是一條記錄,每個居住在該地區的使用者都可以與之相關聯。這允許對多對一和多對多的關係進行建模。 +CODASYL 模型是層次模型的推廣。在層次模型的樹結構中,每條記錄只有一個父節點;在網路模式中,每條記錄可能有多個父節點。例如,“Greater Seattle Area” 地區可能是一條記錄,每個居住在該地區的使用者都可以與之相關聯。這允許對多對一和多對多的關係進行建模。 -網狀模型中記錄之間的連結不是外來鍵,而更像程式語言中的指標(同時仍然儲存在磁碟上)。訪問記錄的唯一方法是跟隨從根記錄起沿這些鏈路所形成的路徑。這被稱為**訪問路徑(access path)**。 +網狀模型中記錄之間的連結不是外來鍵,而更像程式語言中的指標(同時仍然儲存在磁碟上)。訪問記錄的唯一方法是跟隨從根記錄起沿這些鏈路所形成的路徑。這被稱為 **訪問路徑(access path)**。 最簡單的情況下,訪問路徑類似遍歷連結串列:從列表頭開始,每次檢視一條記錄,直到找到所需的記錄。但在多對多關係的情況中,數條不同的路徑可以到達相同的記錄,網狀模型的程式設計師必須跟蹤這些不同的訪問路徑。 -CODASYL中的查詢是透過利用遍歷記錄列和跟隨訪問路徑表在資料庫中移動遊標來執行的。如果記錄有多個父結點(即多個來自其他記錄的傳入指標),則應用程式程式碼必須跟蹤所有的各種關係。甚至CODASYL委員會成員也承認,這就像在n維資料空間中進行導航【17】。 +CODASYL 中的查詢是透過利用遍歷記錄列和跟隨訪問路徑表在資料庫中移動遊標來執行的。如果記錄有多個父結點(即多個來自其他記錄的傳入指標),則應用程式程式碼必須跟蹤所有的各種關係。甚至 CODASYL 委員會成員也承認,這就像在 n 維資料空間中進行導航【17】。 -儘管手動選擇訪問路徑夠能最有效地利用20世紀70年代非常有限的硬體功能(如磁帶驅動器,其搜尋速度非常慢),但這使得查詢和更新資料庫的程式碼變得複雜不靈活。無論是分層還是網狀模型,如果你沒有所需資料的路徑,就會陷入困境。你可以改變訪問路徑,但是必須瀏覽大量手寫資料庫查詢程式碼,並重寫來處理新的訪問路徑。更改應用程式的資料模型是很難的。 +儘管手動選擇訪問路徑夠能最有效地利用 20 世紀 70 年代非常有限的硬體功能(如磁帶驅動器,其搜尋速度非常慢),但這使得查詢和更新資料庫的程式碼變得複雜不靈活。無論是分層還是網狀模型,如果你沒有所需資料的路徑,就會陷入困境。你可以改變訪問路徑,但是必須瀏覽大量手寫資料庫查詢程式碼,並重寫來處理新的訪問路徑。更改應用程式的資料模型是很難的。 #### 關係模型 -相比之下,關係模型做的就是將所有的資料放在光天化日之下:一個 **關係(表)** 只是一個 **元組(行)** 的集合,僅此而已。如果你想讀取資料,它沒有迷宮似的巢狀結構,也沒有複雜的訪問路徑。你可以選中符合任意條件的行,讀取表中的任何或所有行。你可以透過指定某些列作為匹配關鍵字來讀取特定行。你可以在任何表中插入一個新的行,而不必擔心與其他表的外來鍵關係[^iv]。 +相比之下,關係模型做的就是將所有的資料放在光天化日之下:一個 **關係(表)** 只是一個 **元組(行)** 的集合,僅此而已。如果你想讀取資料,它沒有迷宮似的巢狀結構,也沒有複雜的訪問路徑。你可以選中符合任意條件的行,讀取表中的任何或所有行。你可以透過指定某些列作為匹配關鍵字來讀取特定行。你可以在任何表中插入一個新的行,而不必擔心與其他表的外來鍵關係 [^iv]。 -[^iv]: 外來鍵約束允許對修改進行限制,但對於關係模型這並不是必選項。即使有約束,外來鍵連線在查詢時執行,而在CODASYL中,連線在插入時高效完成。 +[^iv]: 外來鍵約束允許對修改進行限制,但對於關係模型這並不是必選項。即使有約束,外來鍵連線在查詢時執行,而在 CODASYL 中,連線在插入時高效完成。 -在關係資料庫中,查詢最佳化器自動決定查詢的哪些部分以哪個順序執行,以及使用哪些索引。這些選擇實際上是“訪問路徑”,但最大的區別在於它們是由查詢最佳化器自動生成的,而不是由程式設計師生成,所以我們很少需要考慮它們。 +在關係資料庫中,查詢最佳化器自動決定查詢的哪些部分以哪個順序執行,以及使用哪些索引。這些選擇實際上是 “訪問路徑”,但最大的區別在於它們是由查詢最佳化器自動生成的,而不是由程式設計師生成,所以我們很少需要考慮它們。 -如果想按新的方式查詢資料,你可以宣告一個新的索引,查詢會自動使用最合適的那些索引。無需更改查詢來利用新的索引(請參閱“[資料查詢語言](#資料查詢語言)”)。關係模型因此使新增應用程式新功能變得更加容易。 +如果想按新的方式查詢資料,你可以宣告一個新的索引,查詢會自動使用最合適的那些索引。無需更改查詢來利用新的索引(請參閱 “[資料查詢語言](#資料查詢語言)”)。關係模型因此使新增應用程式新功能變得更加容易。 -關係資料庫的查詢最佳化器是複雜的,已耗費了多年的研究和開發精力【18】。關係模型的一個關鍵洞察是:只需構建一次查詢最佳化器,隨後使用該資料庫的所有應用程式都可以從中受益。如果你沒有查詢最佳化器的話,那麼為特定查詢手動編寫訪問路徑比編寫通用最佳化器更容易——不過從長期看通用解決方案更好。 +關係資料庫的查詢最佳化器是複雜的,已耗費了多年的研究和開發精力【18】。關係模型的一個關鍵洞察是:只需構建一次查詢最佳化器,隨後使用該資料庫的所有應用程式都可以從中受益。如果你沒有查詢最佳化器的話,那麼為特定查詢手動編寫訪問路徑比編寫通用最佳化器更容易 —— 不過從長期看通用解決方案更好。 #### 與文件資料庫相比 -在一個方面,文件資料庫還原為層次模型:在其父記錄中儲存巢狀記錄([圖2-1](../img/fig2-1.png)中的一對多關係,如`positions`,`education`和`contact_info`),而不是在單獨的表中。 +在一個方面,文件資料庫還原為層次模型:在其父記錄中儲存巢狀記錄([圖 2-1](../img/fig2-1.png) 中的一對多關係,如 `positions`,`education` 和 `contact_info`),而不是在單獨的表中。 -但是,在表示多對一和多對多的關係時,關係資料庫和文件資料庫並沒有根本的不同:在這兩種情況下,相關專案都被一個唯一的識別符號引用,這個識別符號在關係模型中被稱為**外來鍵**,在文件模型中稱為**文件引用**【9】。該識別符號在讀取時透過連線或後續查詢來解析。迄今為止,文件資料庫沒有走CODASYL的老路。 +但是,在表示多對一和多對多的關係時,關係資料庫和文件資料庫並沒有根本的不同:在這兩種情況下,相關專案都被一個唯一的識別符號引用,這個識別符號在關係模型中被稱為 **外來鍵**,在文件模型中稱為 **文件引用**【9】。該識別符號在讀取時透過連線或後續查詢來解析。迄今為止,文件資料庫沒有走 CODASYL 的老路。 ### 關係型資料庫與文件資料庫在今日的對比 -將關係資料庫與文件資料庫進行比較時,可以考慮許多方面的差異,包括它們的容錯屬性(請參閱[第五章](ch5.md))和處理併發性(請參閱[第七章](ch7.md))。本章將只關注資料模型中的差異。 +將關係資料庫與文件資料庫進行比較時,可以考慮許多方面的差異,包括它們的容錯屬性(請參閱 [第五章](ch5.md))和處理併發性(請參閱 [第七章](ch7.md))。本章將只關注資料模型中的差異。 支援文件資料模型的主要論據是架構靈活性,因區域性性而擁有更好的效能,以及對於某些應用程式而言更接近於應用程式使用的資料結構。關係模型透過為連線提供更好的支援以及支援多對一和多對多的關係來反擊。 #### 哪種資料模型更有助於簡化應用程式碼? -如果應用程式中的資料具有類似文件的結構(即,一對多關係樹,通常一次性載入整個樹),那麼使用文件模型可能是一個好主意。將類似文件的結構分解成多個表(如[圖2-1](../img/fig2-1.png)中的`positions`、`education`和`contact_info`)的關係技術可能導致繁瑣的模式和不必要的複雜的應用程式程式碼。 +如果應用程式中的資料具有類似文件的結構(即,一對多關係樹,通常一次性載入整個樹),那麼使用文件模型可能是一個好主意。將類似文件的結構分解成多個表(如 [圖 2-1](../img/fig2-1.png) 中的 `positions`、`education` 和 `contact_info`)的關係技術可能導致繁瑣的模式和不必要的複雜的應用程式程式碼。 -文件模型有一定的侷限性:例如,不能直接引用文件中的巢狀的專案,而是需要說“使用者251的位置列表中的第二項”(很像層次模型中的訪問路徑)。但是,只要檔案巢狀不太深,這通常不是問題。 +文件模型有一定的侷限性:例如,不能直接引用文件中的巢狀的專案,而是需要說 “使用者 251 的位置列表中的第二項”(很像層次模型中的訪問路徑)。但是,只要檔案巢狀不太深,這通常不是問題。 文件資料庫對連線的糟糕支援可能是個問題,也可能不是問題,這取決於應用程式。例如,如果某分析型應用程式使用一個文件資料庫來記錄何時何地發生了何事,那麼多對多關係可能永遠也用不上。【19】。 但如果你的應用程式確實會用到多對多關係,那麼文件模型就沒有那麼誘人了。儘管可以透過反規範化來消除對連線的需求,但這需要應用程式程式碼來做額外的工作以確保資料一致性。儘管應用程式程式碼可以透過向資料庫發出多個請求的方式來模擬連線,但這也將複雜性轉移到應用程式中,而且通常也會比由資料庫內的專用程式碼更慢。在這種情況下,使用文件模型可能會導致更復雜的應用程式碼與更差的效能【15】。 -我們沒有辦法說哪種資料模型更有助於簡化應用程式碼,因為它取決於資料項之間的關係種類。對高度關聯的資料而言,文件模型是極其糟糕的,關係模型是可以接受的,而選用圖形模型(請參閱“[圖資料模型](#圖資料模型)”)是最自然的。 +我們沒有辦法說哪種資料模型更有助於簡化應用程式碼,因為它取決於資料項之間的關係種類。對高度關聯的資料而言,文件模型是極其糟糕的,關係模型是可以接受的,而選用圖形模型(請參閱 “[圖資料模型](#圖資料模型)”)是最自然的。 #### 文件模型中的模式靈活性 -大多數文件資料庫以及關係資料庫中的JSON支援都不會強制文件中的資料採用何種模式。關係資料庫的XML支援通常帶有可選的模式驗證。沒有模式意味著可以將任意的鍵和值新增到文件中,並且當讀取時,客戶端對無法保證文件可能包含的欄位。 +大多數文件資料庫以及關係資料庫中的 JSON 支援都不會強制文件中的資料採用何種模式。關係資料庫的 XML 支援通常帶有可選的模式驗證。沒有模式意味著可以將任意的鍵和值新增到文件中,並且當讀取時,客戶端對無法保證文件可能包含的欄位。 -文件資料庫有時稱為**無模式(schemaless)**,但這具有誤導性,因為讀取資料的程式碼通常假定某種結構——即存在隱式模式,但不由資料庫強制執行【20】。一個更精確的術語是**讀時模式**(即schema-on-read,資料的結構是隱含的,只有在資料被讀取時才被解釋),相應的是**寫時模式**(即schema-on-write,傳統的關係資料庫方法中,模式明確,且資料庫確保所有的資料都符合其模式)【21】。 +文件資料庫有時稱為 **無模式(schemaless)**,但這具有誤導性,因為讀取資料的程式碼通常假定某種結構 —— 即存在隱式模式,但不由資料庫強制執行【20】。一個更精確的術語是 **讀時模式**(即 schema-on-read,資料的結構是隱含的,只有在資料被讀取時才被解釋),相應的是 **寫時模式**(即 schema-on-write,傳統的關係資料庫方法中,模式明確,且資料庫確保所有的資料都符合其模式)【21】。 讀時模式類似於程式語言中的動態(執行時)型別檢查,而寫時模式類似於靜態(編譯時)型別檢查。就像靜態和動態型別檢查的相對優點具有很大的爭議性一樣【22】,資料庫中模式的強制性是一個具有爭議的話題,一般來說沒有正確或錯誤的答案。 @@ -253,7 +253,7 @@ if (user && user.name && !user.first_name) { } ``` -另一方面,在“靜態型別”資料庫模式中,通常會執行以下 **遷移(migration)** 操作: +另一方面,在 “靜態型別” 資料庫模式中,通常會執行以下 **遷移(migration)** 操作: ```sql ALTER TABLE users ADD COLUMN first_name text; @@ -261,11 +261,11 @@ UPDATE users SET first_name = split_part(name, ' ', 1); -- PostgreSQL UPDATE users SET first_name = substring_index(name, ' ', 1); -- MySQL ``` -模式變更的速度很慢,而且要求停運。它的這種壞名譽並不是完全應得的:大多數關係資料庫系統可在幾毫秒內執行`ALTER TABLE`語句。MySQL是一個值得注意的例外,它執行`ALTER TABLE`時會複製整個表,這可能意味著在更改一個大型表時會花費幾分鐘甚至幾個小時的停機時間,儘管存在各種工具來解決這個限制【24,25,26】。 +模式變更的速度很慢,而且要求停運。它的這種壞名譽並不是完全應得的:大多數關係資料庫系統可在幾毫秒內執行 `ALTER TABLE` 語句。MySQL 是一個值得注意的例外,它執行 `ALTER TABLE` 時會複製整個表,這可能意味著在更改一個大型表時會花費幾分鐘甚至幾個小時的停機時間,儘管存在各種工具來解決這個限制【24,25,26】。 -大型表上執行`UPDATE`語句在任何資料庫上都可能會很慢,因為每一行都需要重寫。要是不可接受的話,應用程式可以將`first_name`設定為預設值`NULL`,並在讀取時再填充,就像使用文件資料庫一樣。 +大型表上執行 `UPDATE` 語句在任何資料庫上都可能會很慢,因為每一行都需要重寫。要是不可接受的話,應用程式可以將 `first_name` 設定為預設值 `NULL`,並在讀取時再填充,就像使用文件資料庫一樣。 -當由於某種原因(例如,資料是異構的)集合中的專案並不都具有相同的結構時,讀時模式更具優勢。例如,如果: +當由於某種原因(例如,資料是異構的)集合中的專案並不都具有相同的結構時,讀時模式更具優勢。例如,如果: * 存在許多不同型別的物件,將每種型別的物件放在自己的表中是不現實的。 * 資料的結構由外部系統決定。你無法控制外部系統且它隨時可能變化。 @@ -274,32 +274,32 @@ UPDATE users SET first_name = substring_index(name, ' ', 1); -- MySQL #### 查詢的資料區域性性 -文件通常以單個連續字串形式進行儲存,編碼為JSON、XML或其二進位制變體(如MongoDB的BSON)。如果應用程式經常需要訪問整個文件(例如,將其渲染至網頁),那麼儲存區域性性會帶來效能優勢。如果將資料分割到多個表中(如[圖2-1](../img/fig2-1.png)所示),則需要進行多次索引查詢才能將其全部檢索出來,這可能需要更多的磁碟查詢並花費更多的時間。 +文件通常以單個連續字串形式進行儲存,編碼為 JSON、XML 或其二進位制變體(如 MongoDB 的 BSON)。如果應用程式經常需要訪問整個文件(例如,將其渲染至網頁),那麼儲存區域性性會帶來效能優勢。如果將資料分割到多個表中(如 [圖 2-1](../img/fig2-1.png) 所示),則需要進行多次索引查詢才能將其全部檢索出來,這可能需要更多的磁碟查詢並花費更多的時間。 區域性性僅僅適用於同時需要文件絕大部分內容的情況。資料庫通常需要載入整個文件,即使只訪問其中的一小部分,這對於大型文件來說是很浪費的。更新文件時,通常需要整個重寫。只有不改變文件大小的修改才可以容易地原地執行。因此,通常建議保持相對小的文件,並避免增加文件大小的寫入【9】。這些效能限制大大減少了文件資料庫的實用場景。 -值得指出的是,為了區域性性而分組集合相關資料的想法並不侷限於文件模型。例如,Google的Spanner資料庫在關係資料模型中提供了同樣的區域性性屬性,允許模式宣告一個表的行應該交錯(巢狀)在父表內【27】。Oracle類似地允許使用一個稱為 **多表索引叢集表(multi-table index cluster tables)** 的類似特性【28】。Bigtable資料模型(用於Cassandra和HBase)中的 **列族(column-family)** 概念與管理區域性性的目的類似【29】。 +值得指出的是,為了區域性性而分組集合相關資料的想法並不侷限於文件模型。例如,Google 的 Spanner 資料庫在關係資料模型中提供了同樣的區域性性屬性,允許模式宣告一個表的行應該交錯(巢狀)在父表內【27】。Oracle 類似地允許使用一個稱為 **多表索引叢集表(multi-table index cluster tables)** 的類似特性【28】。Bigtable 資料模型(用於 Cassandra 和 HBase)中的 **列族(column-family)** 概念與管理區域性性的目的類似【29】。 -在[第三章](ch3.md)將還會看到更多關於區域性性的內容。 +在 [第三章](ch3.md) 將還會看到更多關於區域性性的內容。 #### 文件和關係資料庫的融合 -自2000年代中期以來,大多數關係資料庫系統(MySQL除外)都已支援XML。這包括對XML文件進行本地修改的功能,以及在XML文件中進行索引和查詢的功能。這允許應用程式使用那種與文件資料庫應當使用的非常類似的資料模型。 +自 2000 年代中期以來,大多數關係資料庫系統(MySQL 除外)都已支援 XML。這包括對 XML 文件進行本地修改的功能,以及在 XML 文件中進行索引和查詢的功能。這允許應用程式使用那種與文件資料庫應當使用的非常類似的資料模型。 -從9.3版本開始的PostgreSQL 【8】,從5.7版本開始的MySQL以及從版本10.5開始的IBM DB2【30】也對JSON文件提供了類似的支援級別。鑑於用在Web APIs的JSON流行趨勢,其他關係資料庫很可能會跟隨他們的腳步並新增JSON支援。 +從 9.3 版本開始的 PostgreSQL 【8】,從 5.7 版本開始的 MySQL 以及從版本 10.5 開始的 IBM DB2【30】也對 JSON 文件提供了類似的支援級別。鑑於用在 Web APIs 的 JSON 流行趨勢,其他關係資料庫很可能會跟隨他們的腳步並新增 JSON 支援。 -在文件資料庫中,RethinkDB在其查詢語言中支援類似關係的連線,一些MongoDB驅動程式可以自動解析資料庫引用(有效地執行客戶端連線,儘管這可能比在資料庫中執行的連線慢,需要額外的網路往返,並且最佳化更少)。 +在文件資料庫中,RethinkDB 在其查詢語言中支援類似關係的連線,一些 MongoDB 驅動程式可以自動解析資料庫引用(有效地執行客戶端連線,儘管這可能比在資料庫中執行的連線慢,需要額外的網路往返,並且最佳化更少)。 -隨著時間的推移,關係資料庫和文件資料庫似乎變得越來越相似,這是一件好事:資料模型相互補充[^v],如果一個數據庫能夠處理類似文件的資料,並能夠對其執行關係查詢,那麼應用程式就可以使用最符合其需求的功能組合。 +隨著時間的推移,關係資料庫和文件資料庫似乎變得越來越相似,這是一件好事:資料模型相互補充 [^v],如果一個數據庫能夠處理類似文件的資料,並能夠對其執行關係查詢,那麼應用程式就可以使用最符合其需求的功能組合。 關係模型和文件模型的混合是未來資料庫一條很好的路線。 -[^v]: Codd對關係模型【1】的原始描述實際上允許在關係模式中與JSON文件非常相似。他稱之為**非簡單域(nonsimple domains)**。這個想法是,一行中的值不一定是一個像數字或字串一樣的原始資料型別,也可以是一個巢狀的關係(表),因此可以把一個任意巢狀的樹結構作為一個值,這很像30年後新增到SQL中的JSON或XML支援。 +[^v]: Codd 對關係模型【1】的原始描述實際上允許在關係模式中與 JSON 文件非常相似。他稱之為 **非簡單域(nonsimple domains)**。這個想法是,一行中的值不一定是一個像數字或字串一樣的原始資料型別,也可以是一個巢狀的關係(表),因此可以把一個任意巢狀的樹結構作為一個值,這很像 30 年後新增到 SQL 中的 JSON 或 XML 支援。 ## 資料查詢語言 -當引入關係模型時,關係模型包含了一種查詢資料的新方法:SQL是一種 **宣告式** 查詢語言,而IMS和CODASYL使用 **命令式** 程式碼來查詢資料庫。那是什麼意思? +當引入關係模型時,關係模型包含了一種查詢資料的新方法:SQL 是一種 **宣告式** 查詢語言,而 IMS 和 CODASYL 使用 **命令式** 程式碼來查詢資料庫。那是什麼意思? 許多常用的程式語言是命令式的。例如,給定一個動物物種的列表,返回列表中的鯊魚可以這樣寫: @@ -321,7 +321,7 @@ sharks = σ_{family = "sharks"}(animals) $$ σ(希臘字母西格瑪)是選擇運算子,只返回符合條件的動物,`family="shark"`。 -定義SQL時,它緊密地遵循關係代數的結構: +定義 SQL 時,它緊密地遵循關係代數的結構: ```sql SELECT * FROM animals WHERE family ='Sharks'; @@ -329,21 +329,21 @@ SELECT * FROM animals WHERE family ='Sharks'; 命令式語言告訴計算機以特定順序執行某些操作。可以想象一下,逐行地遍歷程式碼,評估條件,更新變數,並決定是否再迴圈一遍。 -在宣告式查詢語言(如SQL或關係代數)中,你只需指定所需資料的模式 - 結果必須符合哪些條件,以及如何將資料轉換(例如,排序,分組和集合) - 但不是如何實現這一目標。資料庫系統的查詢最佳化器決定使用哪些索引和哪些連線方法,以及以何種順序執行查詢的各個部分。 +在宣告式查詢語言(如 SQL 或關係代數)中,你只需指定所需資料的模式 - 結果必須符合哪些條件,以及如何將資料轉換(例如,排序,分組和集合) - 但不是如何實現這一目標。資料庫系統的查詢最佳化器決定使用哪些索引和哪些連線方法,以及以何種順序執行查詢的各個部分。 -宣告式查詢語言是迷人的,因為它通常比命令式API更加簡潔和容易。但更重要的是,它還隱藏了資料庫引擎的實現細節,這使得資料庫系統可以在無需對查詢做任何更改的情況下進行效能提升。 +宣告式查詢語言是迷人的,因為它通常比命令式 API 更加簡潔和容易。但更重要的是,它還隱藏了資料庫引擎的實現細節,這使得資料庫系統可以在無需對查詢做任何更改的情況下進行效能提升。 例如,在本節開頭所示的命令程式碼中,動物列表以特定順序出現。如果資料庫想要在後臺回收未使用的磁碟空間,則可能需要移動記錄,這會改變動物出現的順序。資料庫能否安全地執行,而不會中斷查詢? -SQL示例不確保任何特定的順序,因此不在意順序是否改變。但是如果查詢用命令式的程式碼來寫的話,那麼資料庫就永遠不可能確定程式碼是否依賴於排序。SQL相當有限的功能性為資料庫提供了更多自動最佳化的空間。 +SQL 示例不確保任何特定的順序,因此不在意順序是否改變。但是如果查詢用命令式的程式碼來寫的話,那麼資料庫就永遠不可能確定程式碼是否依賴於排序。SQL 相當有限的功能性為資料庫提供了更多自動最佳化的空間。 -最後,宣告式語言往往適合並行執行。現在,CPU的速度透過核心(core)的增加變得更快,而不是以比以前更高的時鐘速度執行【31】。命令程式碼很難在多個核心和多個機器之間並行化,因為它指定了指令必須以特定順序執行。宣告式語言更具有並行執行的潛力,因為它們僅指定結果的模式,而不指定用於確定結果的演算法。在適當情況下,資料庫可以自由使用查詢語言的並行實現【32】。 +最後,宣告式語言往往適合並行執行。現在,CPU 的速度透過核心(core)的增加變得更快,而不是以比以前更高的時鐘速度執行【31】。命令程式碼很難在多個核心和多個機器之間並行化,因為它指定了指令必須以特定順序執行。宣告式語言更具有並行執行的潛力,因為它們僅指定結果的模式,而不指定用於確定結果的演算法。在適當情況下,資料庫可以自由使用查詢語言的並行實現【32】。 -### Web上的宣告式查詢 +### Web 上的宣告式查詢 -宣告式查詢語言的優勢不僅限於資料庫。為了說明這一點,讓我們在一個完全不同的環境中比較宣告式和命令式方法:一個Web瀏覽器。 +宣告式查詢語言的優勢不僅限於資料庫。為了說明這一點,讓我們在一個完全不同的環境中比較宣告式和命令式方法:一個 Web 瀏覽器。 -假設你有一個關於海洋動物的網站。使用者當前正在檢視鯊魚頁面,因此你將當前所選的導航專案“鯊魚”標記為當前選中專案。 +假設你有一個關於海洋動物的網站。使用者當前正在檢視鯊魚頁面,因此你將當前所選的導航專案 “鯊魚” 標記為當前選中專案。 ```html
      @@ -365,7 +365,7 @@ SQL示例不確保任何特定的順序,因此不在意順序是否改變。
    ``` -現在想讓當前所選頁面的標題具有一個藍色的背景,以便在視覺上突出顯示。使用CSS實現起來非常簡單: +現在想讓當前所選頁面的標題具有一個藍色的背景,以便在視覺上突出顯示。使用 CSS 實現起來非常簡單: ```css li.selected > p { @@ -373,9 +373,9 @@ li.selected > p { } ``` -這裡的CSS選擇器`li.selected> p`聲明瞭我們想要應用藍色樣式的元素的模式:即其直接父元素是具有`selected`CSS類的`
  • `元素的所有`

    `元素。示例中的元素`

    Sharks

    `匹配此模式,但`

    Whales

    `不匹配,因為其`
  • `父元素缺少`class =“selected”`。 +這裡的 CSS 選擇器 `li.selected > p` 聲明瞭我們想要應用藍色樣式的元素的模式:即其直接父元素是具有 CSS 類 `selected` 的 `
  • ` 元素的所有 `

    ` 元素。示例中的元素 `

    Sharks

    ` 匹配此模式,但 `

    Whales

    ` 不匹配,因為其 `
  • ` 父元素缺少 `class="selected"`。 -如果使用XSL而不是CSS,你可以做類似的事情: +如果使用 XSL 而不是 CSS,你可以做類似的事情: ```xml @@ -385,9 +385,9 @@ li.selected > p { ``` -這裡的XPath表示式`li[@class='selected']/p`相當於上例中的CSS選擇器`li.selected> p`。CSS和XSL的共同之處在於,它們都是用於指定文件樣式的宣告式語言。 +這裡的 XPath 表示式 `li[@class='selected']/p` 相當於上例中的 CSS 選擇器 `li.selected > p`。CSS 和 XSL 的共同之處在於,它們都是用於指定文件樣式的宣告式語言。 -想象一下,必須使用命令式方法的情況會是如何。在Javascript中,使用 **文件物件模型(DOM)** API,其結果可能如下所示: +想象一下,必須使用命令式方法的情況會是如何。在 Javascript 中,使用 **文件物件模型(DOM)** API,其結果可能如下所示: ```js var liElements = document.getElementsByTagName("li"); @@ -404,27 +404,27 @@ for (var i = 0; i < liElements.length; i++) { } ``` -這段JavaScript程式碼命令式地將元素設定為藍色背景,但是程式碼看起來很糟糕。不僅比CSS和XSL等價物更長,更難理解,而且還有一些嚴重的問題: +這段 JavaScript 程式碼命令式地將元素設定為藍色背景,但是程式碼看起來很糟糕。不僅比 CSS 和 XSL 等價物更長,更難理解,而且還有一些嚴重的問題: -* 如果選定的類被移除(例如,因為使用者點選了不同的頁面),即使程式碼重新執行,藍色背景也不會被移除 - 因此該專案將保持突出顯示,直到整個頁面被重新載入。使用CSS,瀏覽器會自動檢測`li.selected> p`規則何時不再適用,並在選定的類被移除後立即移除藍色背景。 +* 如果選定的類被移除(例如,因為使用者點選了不同的頁面),即使程式碼重新執行,藍色背景也不會被移除 - 因此該專案將保持突出顯示,直到整個頁面被重新載入。使用 CSS,瀏覽器會自動檢測 `li.selected > p` 規則何時不再適用,並在選定的類被移除後立即移除藍色背景。 -* 如果你想要利用新的API(例如`document.getElementsBy ClassName(“selected”`)甚至`document.evaluate()`)來提高效能,則必須重寫程式碼。另一方面,瀏覽器供應商可以在不破壞相容性的情況下提高CSS和XPath的效能。 +* 如果你想要利用新的 API(例如 `document.getElementsByClassName("selected")` 甚至 `document.evaluate()`)來提高效能,則必須重寫程式碼。另一方面,瀏覽器供應商可以在不破壞相容性的情況下提高 CSS 和 XPath 的效能。 -在Web瀏覽器中,使用宣告式CSS樣式比使用JavaScript命令式地操作樣式要好得多。類似地,在資料庫中,使用像SQL這樣的宣告式查詢語言比使用命令式查詢API要好得多[^vi]。 +在 Web 瀏覽器中,使用宣告式 CSS 樣式比使用 JavaScript 命令式地操作樣式要好得多。類似地,在資料庫中,使用像 SQL 這樣的宣告式查詢語言比使用命令式查詢 API 要好得多 [^vi]。 -[^vi]: IMS和CODASYL都使用命令式API。應用程式通常使用COBOL程式碼遍歷資料庫中的記錄,一次一條記錄【2,16】。 +[^vi]: IMS 和 CODASYL 都使用命令式 API。應用程式通常使用 COBOL 程式碼遍歷資料庫中的記錄,一次一條記錄【2,16】。 -### MapReduce查詢 +### MapReduce 查詢 -MapReduce是一個由Google推廣的程式設計模型,用於在多臺機器上批次處理大規模的資料【33】。一些NoSQL資料儲存(包括MongoDB和CouchDB)支援有限形式的MapReduce,作為在多個文件中執行只讀查詢的機制。 +MapReduce 是一個由 Google 推廣的程式設計模型,用於在多臺機器上批次處理大規模的資料【33】。一些 NoSQL 資料儲存(包括 MongoDB 和 CouchDB)支援有限形式的 MapReduce,作為在多個文件中執行只讀查詢的機制。 -MapReduce將[第十章](ch10.md)中有更詳細的描述。現在我們將簡要討論一下MongoDB使用的模型。 +MapReduce 將 [第十章](ch10.md) 中有更詳細的描述。現在我們將簡要討論一下 MongoDB 使用的模型。 -MapReduce既不是一個宣告式的查詢語言,也不是一個完全命令式的查詢API,而是處於兩者之間:查詢的邏輯用程式碼片段來表示,這些程式碼片段會被處理框架重複性呼叫。它基於`map`(也稱為`collect`)和`reduce`(也稱為`fold`或`inject`)函式,兩個函式存在於許多函數語言程式設計語言中。 +MapReduce 既不是一個宣告式的查詢語言,也不是一個完全命令式的查詢 API,而是處於兩者之間:查詢的邏輯用程式碼片段來表示,這些程式碼片段會被處理框架重複性呼叫。它基於 `map`(也稱為 `collect`)和 `reduce`(也稱為 `fold` 或 `inject`)函式,兩個函式存在於許多函數語言程式設計語言中。 -最好舉例來解釋MapReduce模型。假設你是一名海洋生物學家,每當你看到海洋中的動物時,你都會在資料庫中新增一條觀察記錄。現在你想生成一個報告,說明你每月看到多少鯊魚。 +最好舉例來解釋 MapReduce 模型。假設你是一名海洋生物學家,每當你看到海洋中的動物時,你都會在資料庫中新增一條觀察記錄。現在你想生成一個報告,說明你每月看到多少鯊魚。 -在PostgreSQL中,你可以像這樣表述這個查詢: +在 PostgreSQL 中,你可以像這樣表述這個查詢: ```sql SELECT @@ -435,11 +435,11 @@ WHERE family = 'Sharks' GROUP BY observation_month; ``` -`date_trunc('month',timestamp)`函式用於確定包含`timestamp`的日曆月份,並返回代表該月份開始的另一個時間戳。換句話說,它將時間戳舍入成最近的月份。 +`date_trunc('month',timestamp)` 函式用於確定包含 `timestamp` 的日曆月份,並返回代表該月份開始的另一個時間戳。換句話說,它將時間戳舍入成最近的月份。 這個查詢首先過濾觀察記錄,以只顯示鯊魚家族的物種,然後根據它們發生的日曆月份對觀察記錄果進行分組,最後將在該月的所有觀察記錄中看到的動物數目加起來。 -同樣的查詢用MongoDB的MapReduce功能可以按如下來表述: +同樣的查詢用 MongoDB 的 MapReduce 功能可以按如下來表述: ```js db.observations.mapReduce(function map() { @@ -458,14 +458,14 @@ db.observations.mapReduce(function map() { }); ``` -* 可以宣告式地指定一個只考慮鯊魚種類的過濾器(這是MongoDB特定的MapReduce擴充套件)。 -* 每個匹配查詢的文件都會呼叫一次JavaScript函式`map`,將`this`設定為文件物件。 -* `map`函式發出一個鍵(包括年份和月份的字串,如`"2013-12"`或`"2014-1"`)和一個值(該觀察記錄中的動物數量)。 -* `map`發出的鍵值對按鍵來分組。對於具有相同鍵(即,相同的月份和年份)的所有鍵值對,呼叫一次`reduce`函式。 -* `reduce`函式將特定月份內所有觀測記錄中的動物數量相加。 -* 將最終的輸出寫入到`monthlySharkReport`集合中。 +* 可以宣告式地指定一個只考慮鯊魚種類的過濾器(這是 MongoDB 特定的 MapReduce 擴充套件)。 +* 每個匹配查詢的文件都會呼叫一次 JavaScript 函式 `map`,將 `this` 設定為文件物件。 +* `map` 函式發出一個鍵(包括年份和月份的字串,如 `"2013-12"` 或 `"2014-1"`)和一個值(該觀察記錄中的動物數量)。 +* `map` 發出的鍵值對按鍵來分組。對於具有相同鍵(即,相同的月份和年份)的所有鍵值對,呼叫一次 `reduce` 函式。 +* `reduce` 函式將特定月份內所有觀測記錄中的動物數量相加。 +* 將最終的輸出寫入到 `monthlySharkReport` 集合中。 -例如,假設`observations`集合包含這兩個文件: +例如,假設 `observations` 集合包含這兩個文件: ```json { @@ -482,15 +482,15 @@ db.observations.mapReduce(function map() { } ``` -對每個文件都會呼叫一次`map`函式,結果將是`emit("1995-12",3)`和`emit("1995-12",4)`。隨後,以`reduce("1995-12",[3,4])`呼叫`reduce`函式,將返回`7`。 +對每個文件都會呼叫一次 `map` 函式,結果將是 `emit("1995-12",3)` 和 `emit("1995-12",4)`。隨後,以 `reduce("1995-12",[3,4])` 呼叫 `reduce` 函式,將返回 `7`。 -map和reduce函式在功能上有所限制:它們必須是**純**函式,這意味著它們只使用傳遞給它們的資料作為輸入,它們不能執行額外的資料庫查詢,也不能有任何副作用。這些限制允許資料庫以任何順序執行任何功能,並在失敗時重新執行它們。然而,map和reduce函式仍然是強大的:它們可以解析字串,呼叫庫函式,執行計算等等。 +map 和 reduce 函式在功能上有所限制:它們必須是 **純** 函式,這意味著它們只使用傳遞給它們的資料作為輸入,它們不能執行額外的資料庫查詢,也不能有任何副作用。這些限制允許資料庫以任何順序執行任何功能,並在失敗時重新執行它們。然而,map 和 reduce 函式仍然是強大的:它們可以解析字串,呼叫庫函式,執行計算等等。 -MapReduce是一個相當底層的程式設計模型,用於計算機叢集上的分散式執行。像SQL這樣的更高階的查詢語言可以用一系列的MapReduce操作來實現(見[第十章](ch10.md)),但是也有很多不使用MapReduce的分散式SQL實現。請注意,SQL中沒有任何內容限制它在單個機器上執行,而MapReduce在分散式查詢執行上沒有壟斷權。 +MapReduce 是一個相當底層的程式設計模型,用於計算機叢集上的分散式執行。像 SQL 這樣的更高階的查詢語言可以用一系列的 MapReduce 操作來實現(見 [第十章](ch10.md)),但是也有很多不使用 MapReduce 的分散式 SQL 實現。請注意,SQL 中沒有任何內容限制它在單個機器上執行,而 MapReduce 在分散式查詢執行上沒有壟斷權。 -能夠在查詢中使用JavaScript程式碼是高階查詢的一個重要特性,但這不限於MapReduce,一些SQL資料庫也可以用JavaScript函式進行擴充套件【34】。 +能夠在查詢中使用 JavaScript 程式碼是高階查詢的一個重要特性,但這不限於 MapReduce,一些 SQL 資料庫也可以用 JavaScript 函式進行擴充套件【34】。 -MapReduce的一個可用性問題是,必須編寫兩個密切合作的JavaScript函式,這通常比編寫單個查詢更困難。此外,宣告式查詢語言為查詢最佳化器提供了更多機會來提高查詢的效能。基於這些原因,MongoDB 2.2添加了一種叫做**聚合管道**的宣告式查詢語言的支援【9】。用這種語言表述鯊魚計數查詢如下所示: +MapReduce 的一個可用性問題是,必須編寫兩個密切合作的 JavaScript 函式,這通常比編寫單個查詢更困難。此外,宣告式查詢語言為查詢最佳化器提供了更多機會來提高查詢的效能。基於這些原因,MongoDB 2.2 添加了一種叫做 **聚合管道** 的宣告式查詢語言的支援【9】。用這種語言表述鯊魚計數查詢如下所示: ```js db.observations.aggregate([ @@ -504,7 +504,7 @@ db.observations.aggregate([ ]); ``` -聚合管道語言與SQL的子集具有類似表現力,但是它使用基於JSON的語法而不是SQL的英語句子式語法; 這種差異也許是口味問題。這個故事的寓意是NoSQL系統可能會發現自己意外地重新發明了SQL,儘管帶著偽裝。 +聚合管道語言與 SQL 的子集具有類似表現力,但是它使用基於 JSON 的語法而不是 SQL 的英語句子式語法;這種差異也許是口味問題。這個故事的寓意是 NoSQL 系統可能會發現自己意外地重新發明了 SQL,儘管帶著偽裝。 ## 圖資料模型 @@ -513,7 +513,7 @@ db.observations.aggregate([ 但是,要是多對多關係在你的資料中很常見呢?關係模型可以處理多對多關係的簡單情況,但是隨著資料之間的連線變得更加複雜,將資料建模為圖形顯得更加自然。 -一個圖由兩種物件組成:**頂點**(vertices,也稱為**節點**,即nodes,或**實體**,即entities),和**邊**(edges,也稱為**關係**,即relationships,或**弧**,即arcs)。多種資料可以被建模為一個圖形。典型的例子包括: +一個圖由兩種物件組成:**頂點**(vertices,也稱為 **節點**,即 nodes,或 **實體**,即 entities),和 **邊**(edges,也稱為 **關係**,即 relationships,或 **弧**,即 arcs)。多種資料可以被建模為一個圖形。典型的例子包括: * 社交圖譜 @@ -521,23 +521,23 @@ db.observations.aggregate([ * 網路圖譜 - 頂點是網頁,邊緣表示指向其他頁面的HTML連結。 + 頂點是網頁,邊緣表示指向其他頁面的 HTML 連結。 * 公路或鐵路網路 頂點是交叉路口,邊線代表它們之間的道路或鐵路線。 -可以將那些眾所周知的演算法運用到這些圖上:例如,汽車導航系統搜尋道路網路中兩點之間的最短路徑,PageRank可以用在網路圖上來確定網頁的流行程度,從而確定該網頁在搜尋結果中的排名。 +可以將那些眾所周知的演算法運用到這些圖上:例如,汽車導航系統搜尋道路網路中兩點之間的最短路徑,PageRank 可以用在網路圖上來確定網頁的流行程度,從而確定該網頁在搜尋結果中的排名。 -在剛剛給出的例子中,圖中的所有頂點代表了相同型別的事物(人、網頁或交叉路口)。不過,圖並不侷限於這樣的同類資料:同樣強大地是,圖提供了一種一致的方式,用來在單個數據儲存中儲存完全不同型別的物件。例如,Facebook維護一個包含許多不同型別的頂點和邊的單個圖:頂點表示人,地點,事件,簽到和使用者的評論;邊緣表示哪些人是彼此的朋友,哪個簽到發生在何處,誰評論了哪條訊息,誰參與了哪個事件,等等【35】。 +在剛剛給出的例子中,圖中的所有頂點代表了相同型別的事物(人、網頁或交叉路口)。不過,圖並不侷限於這樣的同類資料:同樣強大地是,圖提供了一種一致的方式,用來在單個數據儲存中儲存完全不同型別的物件。例如,Facebook 維護一個包含許多不同型別的頂點和邊的單個圖:頂點表示人,地點,事件,簽到和使用者的評論;邊緣表示哪些人是彼此的朋友,哪個簽到發生在何處,誰評論了哪條訊息,誰參與了哪個事件,等等【35】。 -在本節中,我們將使用[圖2-5](../img/fig2-5.png)所示的示例。它可以從社交網路或系譜資料庫中獲得:它顯示了兩個人,來自愛達荷州的Lucy和來自法國Beaune的Alain。他們已婚,住在倫敦。 +在本節中,我們將使用 [圖 2-5](../img/fig2-5.png) 所示的示例。它可以從社交網路或系譜資料庫中獲得:它顯示了兩個人,來自愛達荷州的 Lucy 和來自法國 Beaune 的 Alain。他們已婚,住在倫敦。 ![](../img/fig2-5.png) -**圖2-5 圖資料結構示例(框代表頂點,箭頭代表邊)** +**圖 2-5 圖資料結構示例(框代表頂點,箭頭代表邊)** -有幾種不同但相關的方法用來構建和查詢圖表中的資料。在本節中,我們將討論屬性圖模型(由Neo4j,Titan和InfiniteGraph實現)和三元組儲存(triple-store)模型(由Datomic,AllegroGraph等實現)。我們將檢視圖的三種宣告式查詢語言:Cypher,SPARQL和Datalog。除此之外,還有像Gremlin 【36】這樣的圖形查詢語言和像Pregel這樣的圖形處理框架(見[第十章](ch10.md))。 +有幾種不同但相關的方法用來構建和查詢圖表中的資料。在本節中,我們將討論屬性圖模型(由 Neo4j,Titan 和 InfiniteGraph 實現)和三元組儲存(triple-store)模型(由 Datomic,AllegroGraph 等實現)。我們將檢視圖的三種宣告式查詢語言:Cypher,SPARQL 和 Datalog。除此之外,還有像 Gremlin 【36】這樣的圖形查詢語言和像 Pregel 這樣的圖形處理框架(見 [第十章](ch10.md))。 ### 屬性圖 @@ -551,14 +551,14 @@ db.observations.aggregate([ 每條邊(edge)包括: * 唯一識別符號 -* 邊的起點(**尾部頂點**,即tail vertex) -* 邊的終點(**頭部頂點**,即head vertex) +* 邊的起點(**尾部頂點**,即 tail vertex) +* 邊的終點(**頭部頂點**,即 head vertex) * 描述兩個頂點之間關係型別的標籤 * 一組屬性(鍵值對) -可以將圖儲存看作由兩個關係表組成:一個儲存頂點,另一個儲存邊,如[例2-2]()所示(該模式使用PostgreSQL JSON資料型別來儲存每個頂點或每條邊的屬性)。頭部和尾部頂點用來儲存每條邊;如果你想要一組頂點的輸入或輸出邊,你可以分別透過`head_vertex`或`tail_vertex`來查詢`edges`表。 +可以將圖儲存看作由兩個關係表組成:一個儲存頂點,另一個儲存邊,如 [例 2-2]() 所示(該模式使用 PostgreSQL JSON 資料型別來儲存每個頂點或每條邊的屬性)。頭部和尾部頂點用來儲存每條邊;如果你想要一組頂點的輸入或輸出邊,你可以分別透過 `head_vertex` 或 `tail_vertex` 來查詢 `edges` 表。 -**例2-2 使用關係模式來表示屬性圖** +**例 2-2 使用關係模式來表示屬性圖** ```sql CREATE TABLE vertices ( @@ -581,20 +581,20 @@ CREATE INDEX edges_heads ON edges (head_vertex); 關於這個模型的一些重要方面是: 1. 任何頂點都可以有一條邊連線到任何其他頂點。沒有模式限制哪種事物可不可以關聯。 -2. 給定任何頂點,可以高效地找到它的入邊和出邊,從而遍歷圖,即沿著一系列頂點的路徑前後移動(這就是為什麼[例2-2]()在`tail_vertex`和`head_vertex`列上都有索引的原因)。 +2. 給定任何頂點,可以高效地找到它的入邊和出邊,從而遍歷圖,即沿著一系列頂點的路徑前後移動(這就是為什麼 [例 2-2]() 在 `tail_vertex` 和 `head_vertex` 列上都有索引的原因)。 3. 透過對不同型別的關係使用不同的標籤,可以在一個圖中儲存幾種不同的資訊,同時仍然保持一個清晰的資料模型。 -這些特性為資料建模提供了很大的靈活性,如[圖2-5](../img/fig2-5.png)所示。圖中顯示了一些傳統關係模式難以表達的事情,例如不同國家的不同地區結構(法國有省和州,美國有不同的州和州),國中國的怪事(先忽略主權國家和國家錯綜複雜的爛攤子),不同的資料粒度(Lucy現在的住所被指定為一個城市,而她的出生地點只是在一個州的級別)。 +這些特性為資料建模提供了很大的靈活性,如 [圖 2-5](../img/fig2-5.png) 所示。圖中顯示了一些傳統關係模式難以表達的事情,例如不同國家的不同地區結構(法國有省和州,美國有不同的州和州),國中國的怪事(先忽略主權國家和國家錯綜複雜的爛攤子),不同的資料粒度(Lucy 現在的住所被指定為一個城市,而她的出生地點只是在一個州的級別)。 -你可以想象延伸圖還能包括許多關於Lucy和Alain,或其他人的其他更多的事實。例如,你可以用它來表示食物過敏(為每個過敏源增加一個頂點,並增加人與過敏源之間的一條邊來指示一種過敏情況),並連結到過敏源,每個過敏源具有一組頂點用來顯示哪些食物含有哪些物質。然後,你可以寫一個查詢,找出每個人吃什麼是安全的。圖表在可演化性是富有優勢的:當嚮應用程式新增功能時,可以輕鬆擴充套件圖以適應應用程式資料結構的變化。 +你可以想象延伸圖還能包括許多關於 Lucy 和 Alain,或其他人的其他更多的事實。例如,你可以用它來表示食物過敏(為每個過敏源增加一個頂點,並增加人與過敏源之間的一條邊來指示一種過敏情況),並連結到過敏源,每個過敏源具有一組頂點用來顯示哪些食物含有哪些物質。然後,你可以寫一個查詢,找出每個人吃什麼是安全的。圖表在可演化性是富有優勢的:當嚮應用程式新增功能時,可以輕鬆擴充套件圖以適應應用程式資料結構的變化。 -### Cypher查詢語言 +### Cypher 查詢語言 -Cypher是屬性圖的宣告式查詢語言,為Neo4j圖形資料庫而發明【37】(它是以電影“駭客帝國”中的一個角色來命名的,而與密碼術中的密碼無關【38】)。 +Cypher 是屬性圖的宣告式查詢語言,為 Neo4j 圖形資料庫而發明【37】(它是以電影 “駭客帝國” 中的一個角色來命名的,而與密碼術中的密碼無關【38】)。 -[例2-3]()顯示了將[圖2-5](../img/fig2-5.png)的左邊部分插入圖形資料庫的Cypher查詢。可以類似地新增圖的其餘部分,為了便於閱讀而省略。每個頂點都有一個像`USA`或`Idaho`這樣的符號名稱,查詢的其他部分可以使用這些名稱在頂點之間建立邊,使用箭頭符號:`(Idaho) - [:WITHIN] ->(USA)`建立一條標記為`WITHIN`的邊,`Idaho`為尾節點,`USA`為頭節點。 +[例 2-3]() 顯示了將 [圖 2-5](../img/fig2-5.png) 的左邊部分插入圖形資料庫的 Cypher 查詢。可以類似地新增圖的其餘部分,為了便於閱讀而省略。每個頂點都有一個像 `USA` 或 `Idaho` 這樣的符號名稱,查詢的其他部分可以使用這些名稱在頂點之間建立邊,使用箭頭符號:`(Idaho) - [:WITHIN] ->(USA)` 建立一條標記為 `WITHIN` 的邊,`Idaho` 為尾節點,`USA` 為頭節點。 -**例2-3 將圖2-5中的資料子集表示為Cypher查詢** +**例 2-3 將圖 2-5 中的資料子集表示為 Cypher 查詢** ```cypher CREATE @@ -606,11 +606,11 @@ CREATE (Lucy) -[:BORN_IN]-> (Idaho) ``` -當[圖2-5](../img/fig2-5.png)的所有頂點和邊被新增到資料庫後,讓我們提些有趣的問題:例如,找到所有從美國移民到歐洲的人的名字。更確切地說,這裡我們想要找到符合下麵條件的所有頂點,並且返回這些頂點的`name`屬性:該頂點擁有一條連到美國任一位置的`BORN_IN`邊,和一條連到歐洲的任一位置的`LIVING_IN`邊。 +當 [圖 2-5](../img/fig2-5.png) 的所有頂點和邊被新增到資料庫後,讓我們提些有趣的問題:例如,找到所有從美國移民到歐洲的人的名字。更確切地說,這裡我們想要找到符合下麵條件的所有頂點,並且返回這些頂點的 `name` 屬性:該頂點擁有一條連到美國任一位置的 `BORN_IN` 邊,和一條連到歐洲的任一位置的 `LIVING_IN` 邊。 -[例2-4]()展示瞭如何在Cypher中表達這個查詢。在MATCH子句中使用相同的箭頭符號來查詢圖中的模式:`(person) -[:BORN_IN]-> ()` 可以匹配`BORN_IN`邊的任意兩個頂點。該邊的尾節點被綁定了變數`person`,頭節點則未被繫結。 +[例 2-4]() 展示瞭如何在 Cypher 中表達這個查詢。在 MATCH 子句中使用相同的箭頭符號來查詢圖中的模式:`(person) -[:BORN_IN]-> ()` 可以匹配 `BORN_IN` 邊的任意兩個頂點。該邊的尾節點被綁定了變數 `person`,頭節點則未被繫結。 -**例2-4 查詢所有從美國移民到歐洲的人的Cypher查詢:** +**例 2-4 查詢所有從美國移民到歐洲的人的 Cypher 查詢:** ```cypher MATCH @@ -621,36 +621,36 @@ RETURN person.name 查詢按如下來解讀: -> 找到滿足以下兩個條件的所有頂點(稱之為person頂點): -> 1. `person`頂點擁有一條到某個頂點的`BORN_IN`出邊。從那個頂點開始,沿著一系列`WITHIN`出邊最終到達一個型別為`Location`,`name`屬性為`United States`的頂點。 +> 找到滿足以下兩個條件的所有頂點(稱之為 person 頂點): +> 1. `person` 頂點擁有一條到某個頂點的 `BORN_IN` 出邊。從那個頂點開始,沿著一系列 `WITHIN` 出邊最終到達一個型別為 `Location`,`name` 屬性為 `United States` 的頂點。 > -> 2. `person`頂點還擁有一條`LIVES_IN`出邊。沿著這條邊,可以透過一系列`WITHIN`出邊最終到達一個型別為`Location`,`name`屬性為`Europe`的頂點。 +> 2. `person` 頂點還擁有一條 `LIVES_IN` 出邊。沿著這條邊,可以透過一系列 `WITHIN` 出邊最終到達一個型別為 `Location`,`name` 屬性為 `Europe` 的頂點。 > -> 對於這樣的`Person`頂點,返回其`name`屬性。 +> 對於這樣的 `Person` 頂點,返回其 `name` 屬性。 執行這條查詢可能會有幾種可行的查詢路徑。這裡給出的描述建議首先掃描資料庫中的所有人,檢查每個人的出生地和居住地,然後只返回符合條件的那些人。 -等價地,也可以從兩個`Location`頂點開始反向地查詢。假如`name`屬性上有索引,則可以高效地找到代表美國和歐洲的兩個頂點。然後,沿著所有`WITHIN`入邊,可以繼續查找出所有在美國和歐洲的位置(州,地區,城市等)。最後,查找出那些可以由`BORN_IN`或`LIVES_IN`入邊到那些位置頂點的人。 +等價地,也可以從兩個 `Location` 頂點開始反向地查詢。假如 `name` 屬性上有索引,則可以高效地找到代表美國和歐洲的兩個頂點。然後,沿著所有 `WITHIN` 入邊,可以繼續查找出所有在美國和歐洲的位置(州,地區,城市等)。最後,查找出那些可以由 `BORN_IN` 或 `LIVES_IN` 入邊到那些位置頂點的人。 通常對於宣告式查詢語言來說,在編寫查詢語句時,不需要指定執行細節:查詢最佳化程式會自動選擇預測效率最高的策略,因此你可以繼續編寫應用程式的其他部分。 -### SQL中的圖查詢 +### SQL 中的圖查詢 -[例2-2]()建議在關係資料庫中表示圖資料。但是,如果把圖資料放入關係結構中,我們是否也可以使用SQL查詢它? +[例 2-2]() 建議在關係資料庫中表示圖資料。但是,如果把圖資料放入關係結構中,我們是否也可以使用 SQL 查詢它? 答案是肯定的,但有些困難。在關係資料庫中,你通常會事先知道在查詢中需要哪些連線。在圖查詢中,你可能需要在找到待查詢的頂點之前,遍歷可變數量的邊。也就是說,連線的數量事先並不確定。 -在我們的例子中,這發生在Cypher查詢中的`() -[:WITHIN*0..]-> ()`規則中。一個人的`LIVES_IN`邊可以指向任何型別的位置:街道、城市、地區、地區、國家等。一個城市可以在(WITHIN)一個地區內,一個地區可以在(WITHIN)在一個州內,一個州可以在(WITHIN)一個國家內,等等。`LIVES_IN`邊可以直接指向正在查詢的位置,或者一個在位置層次結構中隔了數層的位置。 +在我們的例子中,這發生在 Cypher 查詢中的 `() -[:WITHIN*0..]-> ()` 規則中。一個人的 `LIVES_IN` 邊可以指向任何型別的位置:街道、城市、地區、地區、國家等。一個城市可以在(WITHIN)一個地區內,一個地區可以在(WITHIN)在一個州內,一個州可以在(WITHIN)一個國家內,等等。`LIVES_IN` 邊可以直接指向正在查詢的位置,或者一個在位置層次結構中隔了數層的位置。 -在Cypher中,用`WITHIN*0..`非常簡潔地表述了上述事實:“沿著`WITHIN`邊,零次或多次”。它很像正則表示式中的`*`運算子。 +在 Cypher 中,用 `WITHIN*0..` 非常簡潔地表述了上述事實:“沿著 `WITHIN` 邊,零次或多次”。它很像正則表示式中的 `*` 運算子。 -自SQL:1999,查詢可變長度遍歷路徑的思想可以使用稱為**遞迴公用表表達式**(`WITH RECURSIVE`語法)的東西來表示。[例2-5]()顯示了同樣的查詢 - 查詢從美國移民到歐洲的人的姓名 - 在SQL使用這種技術(PostgreSQL、IBM DB2、Oracle和SQL Server均支援)來表述。但是,與Cypher相比,其語法非常笨拙。 +自 SQL:1999,查詢可變長度遍歷路徑的思想可以使用稱為 **遞迴公用表表達式**(`WITH RECURSIVE` 語法)的東西來表示。[例 2-5]() 顯示了同樣的查詢 - 查詢從美國移民到歐洲的人的姓名 - 在 SQL 使用這種技術(PostgreSQL、IBM DB2、Oracle 和 SQL Server 均支援)來表述。但是,與 Cypher 相比,其語法非常笨拙。 -**例2-5 與示例2-4同樣的查詢,在SQL中使用遞迴公用表表達式表示** +**例 2-5 與示例 2-4 同樣的查詢,在 SQL 中使用遞迴公用表表達式表示** ```sql WITH RECURSIVE - -- in_usa 包含所有的美國境內的位置ID + -- in_usa 包含所有的美國境內的位置 ID in_usa(vertex_id) AS ( SELECT vertex_id FROM vertices WHERE properties ->> 'name' = 'United States' UNION @@ -658,7 +658,7 @@ WITH RECURSIVE JOIN in_usa ON edges.head_vertex = in_usa.vertex_id WHERE edges.label = 'within' ), - -- in_europe 包含所有的歐洲境內的位置ID + -- in_europe 包含所有的歐洲境內的位置 ID in_europe(vertex_id) AS ( SELECT vertex_id FROM vertices WHERE properties ->> 'name' = 'Europe' UNION @@ -666,13 +666,13 @@ WITH RECURSIVE JOIN in_europe ON edges.head_vertex = in_europe.vertex_id WHERE edges.label = 'within' ), - -- born_in_usa 包含了所有型別為Person,且出生在美國的頂點 + -- born_in_usa 包含了所有型別為 Person,且出生在美國的頂點 born_in_usa(vertex_id) AS ( SELECT edges.tail_vertex FROM edges JOIN in_usa ON edges.head_vertex = in_usa.vertex_id WHERE edges.label = 'born_in' ), - -- lives_in_europe 包含了所有型別為Person,且居住在歐洲的頂點。 + -- lives_in_europe 包含了所有型別為 Person,且居住在歐洲的頂點。 lives_in_europe(vertex_id) AS ( SELECT edges.tail_vertex FROM edges JOIN in_europe ON edges.head_vertex = in_europe.vertex_id @@ -684,29 +684,29 @@ WITH RECURSIVE JOIN lives_in_europe ON vertices.vertex_id = lives_in_europe.vertex_id; ``` -* 首先,查詢`name`屬性為`United States`的頂點,將其作為`in_usa`頂點的集合的第一個元素。 -* 從`in_usa`集合的頂點出發,沿著所有的`with_in`入邊,將其尾頂點加入同一集合,不斷遞迴直到所有`with_in`入邊都被訪問完畢。 -* 同理,從`name`屬性為`Europe`的頂點出發,建立`in_europe`頂點的集合。 -* 對於`in_usa`集合中的每個頂點,根據`born_in`入邊來查找出生在美國某個地方的人。 -* 同樣,對於`in_europe`集合中的每個頂點,根據`lives_in`入邊來查詢居住在歐洲的人。 +* 首先,查詢 `name` 屬性為 `United States` 的頂點,將其作為 `in_usa` 頂點的集合的第一個元素。 +* 從 `in_usa` 集合的頂點出發,沿著所有的 `with_in` 入邊,將其尾頂點加入同一集合,不斷遞迴直到所有 `with_in` 入邊都被訪問完畢。 +* 同理,從 `name` 屬性為 `Europe` 的頂點出發,建立 `in_europe` 頂點的集合。 +* 對於 `in_usa` 集合中的每個頂點,根據 `born_in` 入邊來查找出生在美國某個地方的人。 +* 同樣,對於 `in_europe` 集合中的每個頂點,根據 `lives_in` 入邊來查詢居住在歐洲的人。 * 最後,把在美國出生的人的集合與在歐洲居住的人的集合相交。 -同一個查詢,用某一個查詢語言可以寫成4行,而用另一個查詢語言需要29行,這恰恰說明了不同的資料模型是為不同的應用場景而設計的。選擇適合應用程式的資料模型非常重要。 +同一個查詢,用某一個查詢語言可以寫成 4 行,而用另一個查詢語言需要 29 行,這恰恰說明了不同的資料模型是為不同的應用場景而設計的。選擇適合應用程式的資料模型非常重要。 -### 三元組儲存和SPARQL +### 三元組儲存和 SPARQL 三元組儲存模式大體上與屬性圖模型相同,用不同的詞來描述相同的想法。不過仍然值得討論,因為三元組儲存有很多現成的工具和語言,這些工具和語言對於構建應用程式的工具箱可能是寶貴的補充。 -在三元組儲存中,所有資訊都以非常簡單的三部分表示形式儲存(**主語**,**謂語**,**賓語**)。例如,三元組 **(吉姆, 喜歡 ,香蕉)** 中,**吉姆** 是主語,**喜歡** 是謂語(動詞),**香蕉** 是物件。 +在三元組儲存中,所有資訊都以非常簡單的三部分表示形式儲存(**主語**,**謂語**,**賓語**)。例如,三元組 **(吉姆, 喜歡, 香蕉)** 中,**吉姆** 是主語,**喜歡** 是謂語(動詞),**香蕉** 是物件。 三元組的主語相當於圖中的一個頂點。而賓語是下面兩者之一: -1. 原始資料型別中的值,例如字串或數字。在這種情況下,三元組的謂語和賓語相當於主語頂點上的屬性的鍵和值。例如,`(lucy, age, 33)`就像屬性`{“age”:33}`的頂點lucy。 -2. 圖中的另一個頂點。在這種情況下,謂語是圖中的一條邊,主語是其尾部頂點,而賓語是其頭部頂點。例如,在`(lucy, marriedTo, alain)`中主語和賓語`lucy`和`alain`都是頂點,並且謂語`marriedTo`是連線他們的邊的標籤。 +1. 原始資料型別中的值,例如字串或數字。在這種情況下,三元組的謂語和賓語相當於主語頂點上的屬性的鍵和值。例如,`(lucy, age, 33)` 就像屬性 `{“age”:33}` 的頂點 lucy。 +2. 圖中的另一個頂點。在這種情況下,謂語是圖中的一條邊,主語是其尾部頂點,而賓語是其頭部頂點。例如,在 `(lucy, marriedTo, alain)` 中主語和賓語 `lucy` 和 `alain` 都是頂點,並且謂語 `marriedTo` 是連線他們的邊的標籤。 -[例2-6]()顯示了與[例2-3]()相同的資料,以稱為Turtle的格式(Notation3(N3)【39】)的一個子集形式寫成三元組。 +[例 2-6]() 顯示了與 [例 2-3]() 相同的資料,以稱為 Turtle 的格式(Notation3(N3)【39】)的一個子集形式寫成三元組。 -**例2-6 圖2-5中的資料子集,表示為Turtle三元組** +**例 2-6 圖 2-5 中的資料子集,表示為 Turtle 三元組** ```reStructuredText @prefix : . @@ -726,11 +726,11 @@ _:namerica :name "North America" _:namerica :type :"continent" ``` -在這個例子中,圖的頂點被寫為:`_:someName`。這個名字並不意味著這個檔案以外的任何東西。它的存在只是幫助我們明確哪些三元組引用了同一頂點。當謂語表示邊時,該賓語是一個頂點,如`_:idaho :within _:usa.`。當謂語是一個屬性時,該賓語是一個字串,如`_:usa :name "United States"` +在這個例子中,圖的頂點被寫為:`_:someName`。這個名字並不意味著這個檔案以外的任何東西。它的存在只是幫助我們明確哪些三元組引用了同一頂點。當謂語表示邊時,該賓語是一個頂點,如 `_:idaho :within _:usa.`。當謂語是一個屬性時,該賓語是一個字串,如 `_:usa :name"United States"` -一遍又一遍地重複相同的主語看起來相當重複,但幸運的是,可以使用分號來說明關於同一主語的多個事情。這使得Turtle格式相當不錯,可讀性強:請參閱[例2-7]()。 +一遍又一遍地重複相同的主語看起來相當重複,但幸運的是,可以使用分號來說明關於同一主語的多個事情。這使得 Turtle 格式相當不錯,可讀性強:請參閱 [例 2-7]()。 -**例2-7 一種相對例2-6寫入資料的更為簡潔的方法。** +**例 2-7 一種相對例 2-6 寫入資料的更為簡潔的方法。** ``` @prefix : . @@ -742,21 +742,21 @@ _:namerica a :Location; :name "North America"; :type "continent". #### 語義網 -如果你閱讀更多關於三元組儲存的資訊,你可能會被捲入關於語義網的文章漩渦中。三元組儲存資料模型完全獨立於語義網,例如,Datomic【40】是三元組儲存[^vii],並沒有聲稱與它有任何關係。但是,由於在很多人眼中這兩者緊密相連,我們應該簡要地討論一下。 +如果你閱讀更多關於三元組儲存的資訊,你可能會被捲入關於語義網的文章漩渦中。三元組儲存資料模型完全獨立於語義網,例如,Datomic【40】是三元組儲存 [^vii],並沒有聲稱與它有任何關係。但是,由於在很多人眼中這兩者緊密相連,我們應該簡要地討論一下。 -[^vii]: 從技術上講,Datomic使用的是五元組而不是三元組,兩個額外的欄位是用於版本控制的元資料 +[^vii]: 從技術上講,Datomic 使用的是五元組而不是三元組,兩個額外的欄位是用於版本控制的元資料 -從本質上講語義網是一個簡單且合理的想法:網站已經將資訊釋出為文字和圖片供人類閱讀,為什麼不將資訊作為機器可讀的資料也釋出給計算機呢?**資源描述框架**(RDF)【41】的目的是作為不同網站以統一的格式釋出資料的一種機制,允許來自不同網站的資料自動合併成**一個數據網路** - 一種網際網路範圍內的“通用語義網資料庫“。 +從本質上講語義網是一個簡單且合理的想法:網站已經將資訊釋出為文字和圖片供人類閱讀,為什麼不將資訊作為機器可讀的資料也釋出給計算機呢?**資源描述框架**(RDF)【41】的目的是作為不同網站以統一的格式釋出資料的一種機制,允許來自不同網站的資料自動合併成 **一個數據網路** - 一種網際網路範圍內的 “通用語義網資料庫 “。 不幸的是,這個語義網在二十一世紀初被過度使用,但到目前為止沒有任何跡象表明已在實踐中實現,這使得許多人嗤之以鼻。它還遭受了過多的令人眼花繚亂的縮略詞,過於複雜的標準提議和狂妄自大的苦果。 -然而,如果從過去的失敗中汲取教訓,語義網專案還是擁有很多優秀的成果。即使你沒有興趣在語義網上釋出RDF資料,三元組這種模型也是一種好的應用程式內部資料模型。 +然而,如果從過去的失敗中汲取教訓,語義網專案還是擁有很多優秀的成果。即使你沒有興趣在語義網上釋出 RDF 資料,三元組這種模型也是一種好的應用程式內部資料模型。 -#### RDF資料模型 +#### RDF 資料模型 -[例2-7]()中使用的Turtle語言是一種用於RDF資料的人類可讀格式。有時候,RDF也可以以XML格式編寫,不過完成同樣的事情會相對囉嗦,請參閱[例2-8]()。Turtle/N3是更可取的,因為它更容易閱讀,像Apache Jena 【42】這樣的工具可以根據需要在不同的RDF格式之間進行自動轉換。 +[例 2-7]() 中使用的 Turtle 語言是一種用於 RDF 資料的人類可讀格式。有時候,RDF 也可以以 XML 格式編寫,不過完成同樣的事情會相對囉嗦,請參閱 [例 2-8]()。Turtle/N3 是更可取的,因為它更容易閱讀,像 Apache Jena 【42】這樣的工具可以根據需要在不同的 RDF 格式之間進行自動轉換。 -**例2-8 用RDF/XML語法表示例2-7的資料** +**例 2-8 用 RDF/XML 語法表示例 2-7 的資料** ```xml ``` -RDF有一些奇怪之處,因為它是為了在網際網路上交換資料而設計的。三元組的主語,謂語和賓語通常是URI。例如,謂語可能是一個URI,如 ``或``,而不僅僅是`WITHIN`或`LIVES_IN`。這個設計背後的原因為了讓你能夠把你的資料和其他人的資料結合起來,如果他們賦予單詞`within`或者`lives_in`不同的含義,兩者也不會衝突,因為它們的謂語實際上是``和``。 +RDF 有一些奇怪之處,因為它是為了在網際網路上交換資料而設計的。三元組的主語,謂語和賓語通常是 URI。例如,謂語可能是一個 URI,如 `` 或 ``,而不僅僅是 `WITHIN` 或 `LIVES_IN`。這個設計背後的原因為了讓你能夠把你的資料和其他人的資料結合起來,如果他們賦予單詞 `within` 或者 `lives_in` 不同的含義,兩者也不會衝突,因為它們的謂語實際上是 `` 和 ``。 -從RDF的角度來看,URL `` 不一定需要能解析成什麼東西,它只是一個名稱空間。為避免與`http://URL`混淆,本節中的示例使用不可解析的URI,如`urn:example:within`。幸運的是,你只需在檔案頂部指定一個字首,然後就不用再管了。 +從 RDF 的角度來看,URL `` 不一定需要能解析成什麼東西,它只是一個名稱空間。為避免與 `http://URL` 混淆,本節中的示例使用不可解析的 URI,如 `urn:example:within`。幸運的是,你只需在檔案頂部指定一個字首,然後就不用再管了。 -### SPARQL查詢語言 +### SPARQL 查詢語言 -**SPARQL**是一種用於三元組儲存的面向RDF資料模型的查詢語言【43】(它是SPARQL協議和RDF查詢語言的縮寫,發音為“sparkle”)。SPARQL早於Cypher,並且由於Cypher的模式匹配借鑑於SPARQL,這使得它們看起來非常相似【37】。 +**SPARQL** 是一種用於三元組儲存的面向 RDF 資料模型的查詢語言【43】(它是 SPARQL 協議和 RDF 查詢語言的縮寫,發音為 “sparkle”)。SPARQL 早於 Cypher,並且由於 Cypher 的模式匹配借鑑於 SPARQL,這使得它們看起來非常相似【37】。 -與之前相同的查詢 - 查詢從美國轉移到歐洲的人 - 使用SPARQL比使用Cypher甚至更為簡潔(請參閱[例2-9]())。 +與之前相同的查詢 - 查詢從美國轉移到歐洲的人 - 使用 SPARQL 比使用 Cypher 甚至更為簡潔(請參閱 [例 2-9]())。 -**例2-9 與示例2-4相同的查詢,用SPARQL表示** +**例 2-9 與示例 2-4 相同的查詢,用 SPARQL 表示** ```sparql PREFIX : @@ -805,46 +805,46 @@ SELECT ?personName WHERE { } ``` -結構非常相似。以下兩個表示式是等價的(SPARQL中的變數以問號開頭): +結構非常相似。以下兩個表示式是等價的(SPARQL 中的變數以問號開頭): ``` (person) -[:BORN_IN]-> () -[:WITHIN*0..]-> (location) # Cypher ?person :bornIn / :within* ?location. # SPARQL ``` -因為RDF不區分屬性和邊,而只是將它們作為謂語,所以可以使用相同的語法來匹配屬性。在下面的表示式中,變數`usa`被繫結到任意具有值為字串`"United States"`的`name`屬性的頂點: +因為 RDF 不區分屬性和邊,而只是將它們作為謂語,所以可以使用相同的語法來匹配屬性。在下面的表示式中,變數 `usa` 被繫結到任意具有值為字串 `"United States"` 的 `name` 屬性的頂點: ``` (usa {name:'United States'}) # Cypher ?usa :name "United States". # SPARQL ``` -SPARQL是一種很好的查詢語言—儘管SPARQL從未實現語義網,但是它仍然是一種應用程式內部使用的強大工具。 +SPARQL 是一種很好的查詢語言 — 儘管 SPARQL 從未實現語義網,但是它仍然是一種應用程式內部使用的強大工具。 > #### 圖形資料庫與網狀模型相比較 > -> 在“[文件資料庫是否在重蹈覆轍?](#文件資料庫是否在重蹈覆轍?)”中,我們討論了CODASYL和關係模型如何競相解決IMS中的多對多關係問題。乍一看,CODASYL的網狀模型看起來與圖模型相似。CODASYL是否是圖形資料庫的第二個變種? +> 在 “[文件資料庫是否在重蹈覆轍?](#文件資料庫是否在重蹈覆轍?)” 中,我們討論了 CODASYL 和關係模型如何競相解決 IMS 中的多對多關係問題。乍一看,CODASYL 的網狀模型看起來與圖模型相似。CODASYL 是否是圖形資料庫的第二個變種? > > 不,他們在幾個重要方面有所不同: > -> * 在CODASYL中,資料庫有一個模式,用於指定哪種記錄型別可以巢狀在其他記錄型別中。在圖形資料庫中,不存在這樣的限制:任何頂點都可以具有到其他任何頂點的邊。這為應用程式適應不斷變化的需求提供了更大的靈活性。 -> * 在CODASYL中,達到特定記錄的唯一方法是遍歷其中的一個訪問路徑。在圖形資料庫中,可以透過其唯一ID直接引用任何頂點,也可以使用索引來查詢具有特定值的頂點。 -> * 在CODASYL,記錄的後續是一個有序集合,所以資料庫的人不得不維持排序(這會影響儲存佈局),並且插入新記錄到資料庫的應用程式不得不擔心的新記錄在這些集合中的位置。在圖形資料庫中,頂點和邊不是有序的(只能在查詢時對結果進行排序)。 -> * 在CODASYL中,所有查詢都是命令式的,難以編寫,並且很容易因架構中的變化而受到破壞。在圖形資料庫中,如果需要,可以在命令式程式碼中編寫遍歷,但大多數圖形資料庫也支援高階宣告式查詢語言,如Cypher或SPARQL。 +> * 在 CODASYL 中,資料庫有一個模式,用於指定哪種記錄型別可以巢狀在其他記錄型別中。在圖形資料庫中,不存在這樣的限制:任何頂點都可以具有到其他任何頂點的邊。這為應用程式適應不斷變化的需求提供了更大的靈活性。 +> * 在 CODASYL 中,達到特定記錄的唯一方法是遍歷其中的一個訪問路徑。在圖形資料庫中,可以透過其唯一 ID 直接引用任何頂點,也可以使用索引來查詢具有特定值的頂點。 +> * 在 CODASYL,記錄的後續是一個有序集合,所以資料庫的人不得不維持排序(這會影響儲存佈局),並且插入新記錄到資料庫的應用程式不得不擔心的新記錄在這些集合中的位置。在圖形資料庫中,頂點和邊不是有序的(只能在查詢時對結果進行排序)。 +> * 在 CODASYL 中,所有查詢都是命令式的,難以編寫,並且很容易因架構中的變化而受到破壞。在圖形資料庫中,如果需要,可以在命令式程式碼中編寫遍歷,但大多數圖形資料庫也支援高階宣告式查詢語言,如 Cypher 或 SPARQL。 > > ### 基礎:Datalog -**Datalog**是比SPARQL、Cypher更古老的語言,在20世紀80年代被學者廣泛研究【44,45,46】。它在軟體工程師中不太知名,但是它是重要的,因為它為以後的查詢語言提供了基礎。 +**Datalog** 是比 SPARQL、Cypher 更古老的語言,在 20 世紀 80 年代被學者廣泛研究【44,45,46】。它在軟體工程師中不太知名,但是它是重要的,因為它為以後的查詢語言提供了基礎。 -在實踐中,Datalog被用於少數的資料系統中:例如,它是Datomic 【40】的查詢語言,Cascalog 【47】是一種用於查詢Hadoop大資料集的Datalog實現[^viii]。 +在實踐中,Datalog 被用於少數的資料系統中:例如,它是 Datomic 【40】的查詢語言,Cascalog 【47】是一種用於查詢 Hadoop 大資料集的 Datalog 實現 [^viii]。 -[^viii]: Datomic和Cascalog使用Datalog的Clojure S表示式語法。在下面的例子中使用了一個更容易閱讀的Prolog語法,但兩者沒有任何功能差異。 +[^viii]: Datomic 和 Cascalog 使用 Datalog 的 Clojure S 表示式語法。在下面的例子中使用了一個更容易閱讀的 Prolog 語法,但兩者沒有任何功能差異。 -Datalog的資料模型類似於三元組模式,但進行了一點泛化。把三元組寫成**謂語**(**主語,賓語**),而不是寫三元語(**主語,謂語,賓語**)。[例2-10]()顯示瞭如何用Datalog寫入我們的例子中的資料。 +Datalog 的資料模型類似於三元組模式,但進行了一點泛化。把三元組寫成 **謂語**(**主語,賓語**),而不是寫三元語(**主語,謂語,賓語**)。[例 2-10]() 顯示瞭如何用 Datalog 寫入我們的例子中的資料。 -**例2-10 用Datalog來表示圖2-5中的資料子集** +**例 2-10 用 Datalog 來表示圖 2-5 中的資料子集** ```prolog name(namerica, 'North America'). @@ -862,9 +862,9 @@ name(lucy, 'Lucy'). born_in(lucy, idaho). ``` -既然已經定義了資料,我們可以像之前一樣編寫相同的查詢,如[例2-11]()所示。它看起來有點不同於Cypher或SPARQL的等價物,但是請不要放棄它。Datalog是Prolog的一個子集,如果你學過電腦科學,你可能已經見過。 +既然已經定義了資料,我們可以像之前一樣編寫相同的查詢,如 [例 2-11]() 所示。它看起來有點不同於 Cypher 或 SPARQL 的等價物,但是請不要放棄它。Datalog 是 Prolog 的一個子集,如果你學過電腦科學,你可能已經見過。 -**例2-11 與示例2-4相同的查詢,用Datalog表示** +**例 2-11 與示例 2-4 相同的查詢,用 Datalog 表示** ``` within_recursive(Location, Name) :- name(Location, Name). /* Rule 1 */ @@ -881,51 +881,51 @@ migrated(Name, BornIn, LivingIn) :- name(Person, Name), /* Rule 3 */ ?- migrated(Who, 'United States', 'Europe'). /* Who = 'Lucy'. */ ``` -Cypher和SPARQL使用SELECT立即跳轉,但是Datalog一次只進行一小步。我們定義**規則**,以將新謂語告訴資料庫:在這裡,我們定義了兩個新的謂語,`within_recursive`和`migrated`。這些謂語不是儲存在資料庫中的三元組中,而是它們是從資料或其他規則派生而來的。規則可以引用其他規則,就像函式可以呼叫其他函式或者遞迴地呼叫自己一樣。像這樣,複雜的查詢可以一次構建其中的一小塊。 +Cypher 和 SPARQL 使用 SELECT 立即跳轉,但是 Datalog 一次只進行一小步。我們定義 **規則**,以將新謂語告訴資料庫:在這裡,我們定義了兩個新的謂語,`within_recursive` 和 `migrated`。這些謂語不是儲存在資料庫中的三元組中,而是它們是從資料或其他規則派生而來的。規則可以引用其他規則,就像函式可以呼叫其他函式或者遞迴地呼叫自己一樣。像這樣,複雜的查詢可以一次構建其中的一小塊。 -在規則中,以大寫字母開頭的單詞是變數,謂語則用Cypher和SPARQL的方式一樣來匹配。例如,`name(Location, Name)`透過變數繫結`Location = namerica`和`Name ='North America'`可以匹配三元組`name(namerica, 'North America')`。 +在規則中,以大寫字母開頭的單詞是變數,謂語則用 Cypher 和 SPARQL 的方式一樣來匹配。例如,`name(Location, Name)` 透過變數繫結 `Location = namerica` 和 `Name ='North America'` 可以匹配三元組 `name(namerica, 'North America')`。 -要是系統可以在`:-` 運算子的右側找到與所有謂語的一個匹配,就運用該規則。當規則運用時,就好像透過`:-`的左側將其新增到資料庫(將變數替換成它們匹配的值)。 +要是系統可以在 `:-` 運算子的右側找到與所有謂語的一個匹配,就運用該規則。當規則運用時,就好像透過 `:-` 的左側將其新增到資料庫(將變數替換成它們匹配的值)。 因此,一種可能的應用規則的方式是: -1. 資料庫存在`name(namerica, 'North America')`,故運用規則1。它生成`within_recursive(namerica, 'North America')`。 -2. 資料庫存在`within(usa, namerica)`,在上一步驟中生成`within_recursive(namerica, 'North America')`,故運用規則2。它會產生`within_recursive(usa, 'North America')`。 -3. 資料庫存在`within(idaho, usa)`,在上一步生成`within_recursive(usa, 'North America')`,故運用規則2。它產生`within_recursive(idaho, 'North America')`。 +1. 資料庫存在 `name (namerica, 'North America')`,故運用規則 1。它生成 `within_recursive (namerica, 'North America')`。 +2. 資料庫存在 `within (usa, namerica)`,在上一步驟中生成 `within_recursive (namerica, 'North America')`,故運用規則 2。它會產生 `within_recursive (usa, 'North America')`。 +3. 資料庫存在 `within (idaho, usa)`,在上一步生成 `within_recursive (usa, 'North America')`,故運用規則 2。它產生 `within_recursive (idaho, 'North America')`。 -透過重複應用規則1和2,`within_recursive`謂語可以告訴我們在資料庫中包含北美(或任何其他位置名稱)的所有位置。這個過程如[圖2-6](../img/fig2-6.png)所示。 +透過重複應用規則 1 和 2,`within_recursive` 謂語可以告訴我們在資料庫中包含北美(或任何其他位置名稱)的所有位置。這個過程如 [圖 2-6](../img/fig2-6.png) 所示。 ![](../img/fig2-6.png) -**圖2-6 使用示例2-11中的Datalog規則來確定愛達荷州在北美。** +**圖 2-6 使用示例 2-11 中的 Datalog 規則來確定愛達荷州在北美。** -現在規則3可以找到出生在某個地方`BornIn`的人,並住在某個地方`LivingIn`。透過查詢`BornIn ='United States'`和`LivingIn ='Europe'`,並將此人作為變數`Who`,讓Datalog系統找出變數`Who`會出現哪些值。因此,最後得到了與早先的Cypher和SPARQL查詢相同的答案。 +現在規則 3 可以找到出生在某個地方 `BornIn` 的人,並住在某個地方 `LivingIn`。透過查詢 `BornIn ='United States'` 和 `LivingIn ='Europe'`,並將此人作為變數 `Who`,讓 Datalog 系統找出變數 `Who` 會出現哪些值。因此,最後得到了與早先的 Cypher 和 SPARQL 查詢相同的答案。 -相對於本章討論的其他查詢語言,我們需要採取不同的思維方式來思考Datalog方法,但這是一種非常強大的方法,因為規則可以在不同的查詢中進行組合和重用。雖然對於簡單的一次性查詢,顯得不太方便,但是它可以更好地處理資料很複雜的情況。 +相對於本章討論的其他查詢語言,我們需要採取不同的思維方式來思考 Datalog 方法,但這是一種非常強大的方法,因為規則可以在不同的查詢中進行組合和重用。雖然對於簡單的一次性查詢,顯得不太方便,但是它可以更好地處理資料很複雜的情況。 ## 本章小結 資料模型是一個巨大的課題,在本章中,我們快速瀏覽了各種不同的模型。我們沒有足夠的空間來詳細介紹每個模型的細節,但是希望這個概述足以激起你的興趣,以更多地瞭解最適合你的應用需求的模型。 -在歷史上,資料最開始被表示為一棵大樹(層次資料模型),但是這不利於表示多對多的關係,所以發明了關係模型來解決這個問題。最近,開發人員發現一些應用程式也不適合採用關係模型。新的非關係型“NoSQL”資料儲存在兩個主要方向上存在分歧: +在歷史上,資料最開始被表示為一棵大樹(層次資料模型),但是這不利於表示多對多的關係,所以發明了關係模型來解決這個問題。最近,開發人員發現一些應用程式也不適合採用關係模型。新的非關係型 “NoSQL” 資料儲存在兩個主要方向上存在分歧: -1. **文件資料庫**的應用場景是:資料通常是自我包含的,而且文件之間的關係非常稀少。 -2. **圖形資料庫**用於相反的場景:任意事物都可能與任何事物相關聯。 +1. **文件資料庫** 的應用場景是:資料通常是自我包含的,而且文件之間的關係非常稀少。 +2. **圖形資料庫** 用於相反的場景:任意事物都可能與任何事物相關聯。 這三種模型(文件,關係和圖形)在今天都被廣泛使用,並且在各自的領域都發揮很好。一個模型可以用另一個模型來模擬 — 例如,圖資料可以在關係資料庫中表示 — 但結果往往是糟糕的。這就是為什麼我們有著針對不同目的的不同系統,而不是一個單一的萬能解決方案。 文件資料庫和圖資料庫有一個共同點,那就是它們通常不會為儲存的資料強制一個模式,這可以使應用程式更容易適應不斷變化的需求。但是應用程式很可能仍會假定資料具有一定的結構;這只是模式是明確的(寫入時強制)還是隱含的(讀取時處理)的問題。 -每個資料模型都具有各自的查詢語言或框架,我們討論了幾個例子:SQL,MapReduce,MongoDB的聚合管道,Cypher,SPARQL和Datalog。我們也談到了CSS和XSL/XPath,它們不是資料庫查詢語言,而包含有趣的相似之處。 +每個資料模型都具有各自的查詢語言或框架,我們討論了幾個例子:SQL,MapReduce,MongoDB 的聚合管道,Cypher,SPARQL 和 Datalog。我們也談到了 CSS 和 XSL/XPath,它們不是資料庫查詢語言,而包含有趣的相似之處。 雖然我們已經覆蓋了很多層面,但仍然有許多資料模型沒有提到。舉幾個簡單的例子: -* 使用基因組資料的研究人員通常需要執行**序列相似性搜尋**,這意味著需要一個很長的字串(代表一個DNA分子),並在一個擁有類似但不完全相同的字串的大型資料庫中尋找匹配。這裡所描述的資料庫都不能處理這種用法,這就是為什麼研究人員編寫了像GenBank這樣的專門的基因組資料庫軟體的原因【48】。 +* 使用基因組資料的研究人員通常需要執行 **序列相似性搜尋**,這意味著需要一個很長的字串(代表一個 DNA 分子),並在一個擁有類似但不完全相同的字串的大型資料庫中尋找匹配。這裡所描述的資料庫都不能處理這種用法,這就是為什麼研究人員編寫了像 GenBank 這樣的專門的基因組資料庫軟體的原因【48】。 * 粒子物理學家數十年來一直在進行大資料型別的大規模資料分析,像大型強子對撞機(LHC)這樣的專案現在可以工作在數百億兆位元組的範圍內!在這樣的規模下,需要定製解決方案來阻止硬體成本的失控【49】。 -* **全文搜尋**可以說是一種經常與資料庫一起使用的資料模型。資訊檢索是一個很大的專業課題,我們不會在本書中詳細介紹,但是我們將在第三章和第三部分中介紹搜尋索引。 +* **全文搜尋** 可以說是一種經常與資料庫一起使用的資料模型。資訊檢索是一個很大的專業課題,我們不會在本書中詳細介紹,但是我們將在第三章和第三部分中介紹搜尋索引。 -讓我們暫時將其放在一邊。在[下一章](ch3.md)中,我們將討論在**實現**本章描述的資料模型時會遇到的一些權衡。 +讓我們暫時將其放在一邊。在 [下一章](ch3.md) 中,我們將討論在 **實現** 本章描述的資料模型時會遇到的一些權衡。 ## 參考文獻 @@ -980,4 +980,4 @@ Cypher和SPARQL使用SELECT立即跳轉,但是Datalog一次只進行一小步 | 上一章 | 目錄 | 下一章 | | -------------------------------------------- | ------------------------------- | ---------------------------- | -| [第一章:可靠性、可伸縮性、可維護性](ch1.md) | [設計資料密集型應用](README.md) | [第三章:儲存與檢索](ch3.md) | +| [第一章:可靠性、可伸縮性、可維護性](ch1.md) | [設計資料密集型應用](README.md) | [第三章:儲存與檢索](ch3.md) | \ No newline at end of file diff --git a/zh-tw/colophon.md b/zh-tw/colophon.md index 16323728..5e0f286d 100644 --- a/zh-tw/colophon.md +++ b/zh-tw/colophon.md @@ -2,9 +2,9 @@ ## 關於作者 -**Martin Kleppmann**是英國劍橋大學分散式系統的研究員。此前他曾在網際網路公司擔任過軟體工程師和企業家,其中包括LinkedIn和Rapportive,負責大規模資料基礎架構。在這個過程中,他以艱難的方式學習了一些東西,他希望這本書能夠讓你避免重蹈覆轍。 +**Martin Kleppmann** 是英國劍橋大學分散式系統的研究員。此前他曾在網際網路公司擔任過軟體工程師和企業家,其中包括 LinkedIn 和 Rapportive,負責大規模資料基礎架構。在這個過程中,他以艱難的方式學習了一些東西,他希望這本書能夠讓你避免重蹈覆轍。 -Martin是一位常規會議演講者,博主和開源貢獻者。他認為,每個人都應該有深刻的技術理念,深層次的理解能幫助我們開發出更好的軟體。 +Martin 是一位常規會議演講者,博主和開源貢獻者。他認為,每個人都應該有深刻的技術理念,深層次的理解能幫助我們開發出更好的軟體。 ![](http://martin.kleppmann.com/2017/03/ddia-poster.jpg) @@ -20,14 +20,14 @@ Alibaba+-Finplus 架構師/全棧工程師 (2015 ~ 2017) ## 後記 -《設計資料密集型應用》封面上的動物是**印度野豬(Sus scrofa cristatus)**,它是在印度、緬甸、尼泊爾、斯里蘭卡和泰國發現的一種野豬的亞種。與歐洲野豬不同,它們有更高的背部鬃毛,沒有體表絨毛,以及更大更直的頭骨。 +《設計資料密集型應用》封面上的動物是 **印度野豬(Sus scrofa cristatus)**,它是在印度、緬甸、尼泊爾、斯里蘭卡和泰國發現的一種野豬的亞種。與歐洲野豬不同,它們有更高的背部鬃毛,沒有體表絨毛,以及更大更直的頭骨。 -印度野豬有一頭灰色或黑色的頭髮,脊背上有短而硬的毛。雄性有突出的犬齒(稱為T),用來與對手戰鬥或抵禦掠食者。雄性比雌性大,這些物種平均肩高33-35英寸,體重200-300磅。他們的天敵包括熊、老虎和各種大型貓科動物。 +印度野豬有一頭灰色或黑色的頭髮,脊背上有短而硬的毛。雄性有突出的犬齒(稱為 T),用來與對手戰鬥或抵禦掠食者。雄性比雌性大,這些物種平均肩高 33-35 英寸,體重 200-300 磅。他們的天敵包括熊、老虎和各種大型貓科動物。 -這些動物夜行且雜食——它們吃各種各樣的東西,包括根、昆蟲、腐肉、堅果、漿果和小動物。野豬經常因為破壞農作物的根被人們所熟知,他們造成大量的破壞,並被農民所敵視。他們每天需要攝入4,000 ~ 4,500卡路里的能量。野豬有發達的嗅覺,這有助於尋找地下植物和挖掘動物。然而,它們的視力很差。 +這些動物夜行且雜食 —— 它們吃各種各樣的東西,包括根、昆蟲、腐肉、堅果、漿果和小動物。野豬經常因為破壞農作物的根被人們所熟知,他們造成大量的破壞,並被農民所敵視。他們每天需要攝入 4,000 ~ 4,500 卡路里的能量。野豬有發達的嗅覺,這有助於尋找地下植物和挖掘動物。然而,它們的視力很差。 野豬在人類文化中一直具有重要意義。在印度教傳說中,野豬是毗溼奴神的化身。在古希臘的喪葬紀念碑中,它是一個勇敢失敗者的象徵(與勝利的獅子相反)。由於它的侵略,它被描繪在斯堪的納維亞、日耳曼和盎格魯撒克遜戰士的盔甲和武器上。在中國十二生肖中,它象徵著決心和急躁。 -O'Reilly封面上的許多動物都受到威脅,這些動物對世界都很重要。要了解有關如何提供幫助的更多資訊,請訪問animals.oreilly.com。 +O'Reilly 封面上的許多動物都受到威脅,這些動物對世界都很重要。要了解有關如何提供幫助的更多資訊,請訪問 animals.oreilly.com。 -封面圖片來自Shaw's Zoology。封面字型是URW Typewriter和Guardian Sans。文字字型是Adobe Minion Pro;圖中的字型是Adobe Myriad Pro;標題字型是Adobe Myriad Condensed;程式碼字型是Dalton Maag的Ubuntu Mono。 \ No newline at end of file +封面圖片來自 Shaw's Zoology。封面字型是 URW Typewriter 和 Guardian Sans。文字字型是 Adobe Minion Pro;圖中的字型是 Adobe Myriad Pro;標題字型是 Adobe Myriad Condensed;程式碼字型是 Dalton Maag 的 Ubuntu Mono。 \ No newline at end of file diff --git a/zh-tw/glossary.md b/zh-tw/glossary.md index 39891391..34761ef9 100644 --- a/zh-tw/glossary.md +++ b/zh-tw/glossary.md @@ -5,7 +5,7 @@ * **非同步(asynchronous)** - 不等待某些事情完成(例如,將資料傳送到網路中的另一個節點),並且不會假設要花多長時間。請參閱[同步複製與非同步複製](ch5.md#同步複製與非同步複製)”,“[同步網路與非同步網路](ch8.md#同步網路與非同步網路)”,以及“[系統模型與現實](ch8.md#系統模型與現實)”。 + 不等待某些事情完成(例如,將資料傳送到網路中的另一個節點),並且不會假設要花多長時間。請參閱“[同步複製與非同步複製](ch5.md#同步複製與非同步複製)”、“[同步網路與非同步網路](ch8.md#同步網路與非同步網路)”以及“[系統模型與現實](ch8.md#系統模型與現實)”。 * **原子(atomic)** diff --git a/zh-tw/part-i.md b/zh-tw/part-i.md index 13de8688..4324d9b3 100644 --- a/zh-tw/part-i.md +++ b/zh-tw/part-i.md @@ -2,12 +2,12 @@ 本書前四章介紹了資料系統底層的基礎概念,無論是在單臺機器上執行的單點資料系統,還是分佈在多臺機器上的分散式資料系統都適用。 -1. [第一章](ch1.md)將介紹本書使用的術語和方法。**可靠性,可伸縮性和可維護性** ,這些詞彙到底意味著什麼?如何實現這些目標? -2. [第二章](ch2.md)將對幾種不同的**資料模型和查詢語言**進行比較。從程式設計師的角度看,這是資料庫之間最明顯的區別。不同的資料模型適用於不同的應用場景。 -3. [第三章](ch3.md)將深入**儲存引擎**內部,研究資料庫如何在磁碟上擺放資料。不同的儲存引擎針對不同的負載進行最佳化,選擇合適的儲存引擎對系統性能有巨大影響。 -4. [第四章](ch4)將對幾種不同的 **資料編碼**進行比較。特別研究了這些格式在應用需求經常變化、模式需要隨時間演變的環境中表現如何。 +1. [第一章](ch1.md) 將介紹本書使用的術語和方法。**可靠性,可伸縮性和可維護性** ,這些詞彙到底意味著什麼?如何實現這些目標? +2. [第二章](ch2.md) 將對幾種不同的 **資料模型和查詢語言** 進行比較。從程式設計師的角度看,這是資料庫之間最明顯的區別。不同的資料模型適用於不同的應用場景。 +3. [第三章](ch3.md) 將深入 **儲存引擎** 內部,研究資料庫如何在磁碟上擺放資料。不同的儲存引擎針對不同的負載進行最佳化,選擇合適的儲存引擎對系統性能有巨大影響。 +4. [第四章](ch4) 將對幾種不同的 **資料編碼** 進行比較。特別研究了這些格式在應用需求經常變化、模式需要隨時間演變的環境中表現如何。 -第二部分將專門討論在**分散式資料系統**中特有的問題。 +第二部分將專門討論在 **分散式資料系統** 中特有的問題。 ## 目錄 diff --git a/zh-tw/part-ii.md b/zh-tw/part-ii.md index dd0a9f41..47345a66 100644 --- a/zh-tw/part-ii.md +++ b/zh-tw/part-ii.md @@ -2,12 +2,12 @@ > 一個成功的技術,現實的優先順序必須高於公關,你可以糊弄別人,但糊弄不了自然規律。 > -> ——羅傑斯委員會報告(1986) +> —— 羅傑斯委員會報告(1986) > ------- -在本書的[第一部分](part-i.md)中,我們討論了資料系統的各個方面,但僅限於資料儲存在單臺機器上的情況。現在我們到了[第二部分](part-ii.md),進入更高的層次,並提出一個問題:如果**多臺機器**參與資料的儲存和檢索,會發生什麼? +在本書的 [第一部分](part-i.md) 中,我們討論了資料系統的各個方面,但僅限於資料儲存在單臺機器上的情況。現在我們到了 [第二部分](part-ii.md),進入更高的層次,並提出一個問題:如果 **多臺機器** 參與資料的儲存和檢索,會發生什麼? 你可能會出於各種各樣的原因,希望將資料庫分佈到多臺機器上: @@ -25,27 +25,27 @@ ## 伸縮至更高的載荷 -如果你需要的只是伸縮至更高的**載荷(load)**,最簡單的方法就是購買更強大的機器(有時稱為**垂直伸縮**,即vertical scaling,或**向上伸縮**,即scale up)。許多處理器,記憶體和磁碟可以在同一個作業系統下相互連線,快速的相互連線允許任意處理器訪問記憶體或磁碟的任意部分。在這種 **共享記憶體架構(shared-memory architecture)** 中,所有的元件都可以看作一臺單獨的機器[^i]。 +如果你需要的只是伸縮至更高的 **載荷(load)**,最簡單的方法就是購買更強大的機器(有時稱為 **垂直伸縮**,即 vertical scaling,或 **向上伸縮**,即 scale up)。許多處理器,記憶體和磁碟可以在同一個作業系統下相互連線,快速的相互連線允許任意處理器訪問記憶體或磁碟的任意部分。在這種 **共享記憶體架構(shared-memory architecture)** 中,所有的元件都可以看作一臺單獨的機器 [^i]。 -[^i]: 在大型機中,儘管任意處理器都可以訪問記憶體的任意部分,但總有一些記憶體區域與一些處理器更接近(稱為**非均勻記憶體訪問(nonuniform memory access, NUMA)**【1】)。 為了有效利用這種架構特性,需要對處理進行細分,以便每個處理器主要訪問臨近的記憶體,這意味著即使表面上看起來只有一臺機器在執行,**分割槽(partitioning)** 仍然是必要的。 +[^i]: 在大型機中,儘管任意處理器都可以訪問記憶體的任意部分,但總有一些記憶體區域與一些處理器更接近(稱為 **非均勻記憶體訪問(nonuniform memory access, NUMA)**【1】)。 為了有效利用這種架構特性,需要對處理進行細分,以便每個處理器主要訪問臨近的記憶體,這意味著即使表面上看起來只有一臺機器在執行,**分割槽(partitioning)** 仍然是必要的。 共享記憶體方法的問題在於,成本增長速度快於線性增長:一臺有著雙倍處理器數量,雙倍記憶體大小,雙倍磁碟容量的機器,通常成本會遠遠超過原來的兩倍。而且可能因為存在瓶頸,並不足以處理雙倍的載荷。 -共享記憶體架構可以提供有限的容錯能力,高階機器可以使用熱插拔的元件(不關機更換磁碟,記憶體模組,甚至處理器)——但它必然囿於單個地理位置的桎梏。 +共享記憶體架構可以提供有限的容錯能力,高階機器可以使用熱插拔的元件(不關機更換磁碟,記憶體模組,甚至處理器)—— 但它必然囿於單個地理位置的桎梏。 -另一種方法是**共享磁碟架構(shared-disk architecture)**,它使用多臺具有獨立處理器和記憶體的機器,但將資料儲存在機器之間共享的磁碟陣列上,這些磁碟透過快速網路連線[^ii]。這種架構用於某些資料倉庫,但競爭和鎖定的開銷限制了共享磁碟方法的可伸縮性【2】。 +另一種方法是 **共享磁碟架構(shared-disk architecture)**,它使用多臺具有獨立處理器和記憶體的機器,但將資料儲存在機器之間共享的磁碟陣列上,這些磁碟透過快速網路連線 [^ii]。這種架構用於某些資料倉庫,但競爭和鎖定的開銷限制了共享磁碟方法的可伸縮性【2】。 -[^ii]: 網路附屬儲存(Network Attached Storage, NAS),或**儲存區網路(Storage Area Network, SAN)** +[^ii]: 網路附屬儲存(Network Attached Storage, NAS),或 **儲存區網路(Storage Area Network, SAN)** ### 無共享架構 -相比之下,**無共享架構**【3】(shared-nothing architecture,有時被稱為**水平伸縮**,即horizontal scaling,或**向外伸縮**,即scaling out)已經相當普及。在這種架構中,執行資料庫軟體的每臺機器/虛擬機器都稱為**節點(node)**。每個節點只使用各自的處理器,記憶體和磁碟。節點之間的任何協調,都是在軟體層面使用傳統網路實現的。 +相比之下,**無共享架構**【3】(shared-nothing architecture,有時被稱為 **水平伸縮**,即 horizontal scaling,或 **向外伸縮**,即 scaling out)已經相當普及。在這種架構中,執行資料庫軟體的每臺機器 / 虛擬機器都稱為 **節點(node)**。每個節點只使用各自的處理器,記憶體和磁碟。節點之間的任何協調,都是在軟體層面使用傳統網路實現的。 -無共享系統不需要使用特殊的硬體,所以你可以用任意機器——比如價效比最好的機器。你也許可以跨多個地理區域分佈資料從而減少使用者延遲,或者在損失一整個資料中心的情況下倖免於難。隨著雲端虛擬機器部署的出現,即使是小公司,現在無需Google級別的運維,也可以實現異地分散式架構。 +無共享系統不需要使用特殊的硬體,所以你可以用任意機器 —— 比如價效比最好的機器。你也許可以跨多個地理區域分佈資料從而減少使用者延遲,或者在損失一整個資料中心的情況下倖免於難。隨著雲端虛擬機器部署的出現,即使是小公司,現在無需 Google 級別的運維,也可以實現異地分散式架構。 -在這一部分裡,我們將重點放在無共享架構上。它不見得是所有場景的最佳選擇,但它是最需要你謹慎從事的架構。如果你的資料分佈在多個節點上,你需要意識到這樣一個分散式系統中約束和權衡 ——資料庫並不能魔術般地把這些東西隱藏起來。 +在這一部分裡,我們將重點放在無共享架構上。它不見得是所有場景的最佳選擇,但它是最需要你謹慎從事的架構。如果你的資料分佈在多個節點上,你需要意識到這樣一個分散式系統中約束和權衡 —— 資料庫並不能魔術般地把這些東西隱藏起來。 -雖然分散式無共享架構有許多優點,但它通常也會給應用帶來額外的複雜度,有時也會限制你可用資料模型的表達力。在某些情況下,一個簡單的單執行緒程式可以比一個擁有超過100個CPU核的叢集表現得更好【4】。另一方面,無共享系統可以非常強大。接下來的幾章,將詳細討論分散式資料會帶來的問題。 +雖然分散式無共享架構有許多優點,但它通常也會給應用帶來額外的複雜度,有時也會限制你可用資料模型的表達力。在某些情況下,一個簡單的單執行緒程式可以比一個擁有超過 100 個 CPU 核的叢集表現得更好【4】。另一方面,無共享系統可以非常強大。接下來的幾章,將詳細討論分散式資料會帶來的問題。 ### 複製 vs 分割槽 @@ -53,21 +53,21 @@ * 複製(Replication) - 在幾個不同的節點上儲存資料的相同副本,可能放在不同的位置。 複製提供了冗餘:如果一些節點不可用,剩餘的節點仍然可以提供資料服務。 複製也有助於改善效能。 [第五章](ch5.md)將討論複製。 + 在幾個不同的節點上儲存資料的相同副本,可能放在不同的位置。 複製提供了冗餘:如果一些節點不可用,剩餘的節點仍然可以提供資料服務。 複製也有助於改善效能。 [第五章](ch5.md) 將討論複製。 * 分割槽 (Partitioning) - 將一個大型資料庫拆分成較小的子集(稱為**分割槽**,即partitions),從而不同的分割槽可以指派給不同的**節點**(nodes,亦稱**分片**,即sharding)。 [第六章](ch6.md)將討論分割槽。 + 將一個大型資料庫拆分成較小的子集(稱為 **分割槽**,即 partitions),從而不同的分割槽可以指派給不同的 **節點**(nodes,亦稱 **分片**,即 sharding)。 [第六章](ch6.md) 將討論分割槽。 -複製和分割槽是不同的機制,但它們經常同時使用。如[圖II-1](../img/figii-1.png)所示。 +複製和分割槽是不同的機制,但它們經常同時使用。如 [圖 II-1](../img/figii-1.png) 所示。 ![](../img/figii-1.png) -**圖II-1 一個數據庫切分為兩個分割槽,每個分割槽都有兩個副本** +**圖 II-1 一個數據庫切分為兩個分割槽,每個分割槽都有兩個副本** -理解了這些概念,就可以開始討論在分散式系統中需要做出的困難抉擇。[第七章](ch7.md)將討論**事務(Transaction)**,這對於瞭解資料系統中可能出現的各種問題,以及我們可以做些什麼很有幫助。[第八章](ch8.md)和[第九章](ch9.md)將討論分散式系統的根本侷限性。 +理解了這些概念,就可以開始討論在分散式系統中需要做出的困難抉擇。[第七章](ch7.md) 將討論 **事務 (Transaction)**,這對於瞭解資料系統中可能出現的各種問題,以及我們可以做些什麼很有幫助。[第八章](ch8.md) 和 [第九章](ch9.md) 將討論分散式系統的根本侷限性。 -在本書的[第三部分](part-iii.md)中,將討論如何將多個(可能是分散式的)資料儲存整合為一個更大的系統,以滿足複雜的應用需求。 但首先,我們來聊聊分散式的資料。 +在本書的 [第三部分](part-iii.md) 中,將討論如何將多個(可能是分散式的)資料儲存整合為一個更大的系統,以滿足複雜的應用需求。 但首先,我們來聊聊分散式的資料。 ## 索引 diff --git a/zh-tw/part-iii.md b/zh-tw/part-iii.md index 721a9b72..55329246 100644 --- a/zh-tw/part-iii.md +++ b/zh-tw/part-iii.md @@ -1,6 +1,6 @@ # 第三部分:衍生資料 -在本書的[第一部分](part-i.md)和[第二部分](part-ii.md)中,我們自底向上地把所有關於分散式資料庫的主要考量都過了一遍。從資料在磁碟上的佈局,一直到出現故障時分散式系統一致性的侷限。但所有的討論都假定了應用中只用了一種資料庫。 +在本書的 [第一部分](part-i.md) 和 [第二部分](part-ii.md) 中,我們自底向上地把所有關於分散式資料庫的主要考量都過了一遍。從資料在磁碟上的佈局,一直到出現故障時分散式系統一致性的侷限。但所有的討論都假定了應用中只用了一種資料庫。 現實世界中的資料系統往往更為複雜。大型應用程式經常需要以多種方式訪問和處理資料,沒有一個數據庫可以同時滿足所有這些不同的需求。因此應用程式通常組合使用多種元件:資料儲存,索引,快取,分析系統,等等,並實現在這些元件中移動資料的機制。 @@ -12,15 +12,15 @@ * 記錄系統(System of record) - **記錄系統**,也被稱為**真相源(source of truth)**,持有資料的權威版本。當新的資料進入時(例如,使用者輸入)首先會記錄在這裡。每個事實正正好好表示一次(表示通常是**正規化的**,即normalized)。如果其他系統和**記錄系統**之間存在任何差異,那麼記錄系統中的值是正確的(根據定義)。 + **記錄系統**,也被稱為 **真相源(source of truth)**,持有資料的權威版本。當新的資料進入時(例如,使用者輸入)首先會記錄在這裡。每個事實正正好好表示一次(表示通常是 **正規化的**,即 normalized)。如果其他系統和 **記錄系統** 之間存在任何差異,那麼記錄系統中的值是正確的(根據定義)。 * 衍生資料系統(Derived data systems) - **衍生系統**中的資料,通常是另一個系統中的現有資料以某種方式進行轉換或處理的結果。如果丟失衍生資料,可以從原始來源重新建立。典型的例子是**快取(cache)**:如果資料在快取中,就可以由快取提供服務;如果快取不包含所需資料,則降級由底層資料庫提供。非規範化的值,索引和物化檢視亦屬此類。在推薦系統中,預測彙總資料通常衍生自使用者日誌。 + **衍生系統** 中的資料,通常是另一個系統中的現有資料以某種方式進行轉換或處理的結果。如果丟失衍生資料,可以從原始來源重新建立。典型的例子是 **快取(cache)**:如果資料在快取中,就可以由快取提供服務;如果快取不包含所需資料,則降級由底層資料庫提供。非規範化的值,索引和物化檢視亦屬此類。在推薦系統中,預測彙總資料通常衍生自使用者日誌。 -從技術上講,衍生資料是**冗餘的(redundant)**,因為它重複了已有的資訊。但是衍生資料對於獲得良好的只讀查詢效能通常是至關重要的。它通常是非規範化的。可以從單個源頭衍生出多個不同的資料集,使你能從不同的“視角”洞察資料。 +從技術上講,衍生資料是 **冗餘的(redundant)**,因為它重複了已有的資訊。但是衍生資料對於獲得良好的只讀查詢效能通常是至關重要的。它通常是非規範化的。可以從單個源頭衍生出多個不同的資料集,使你能從不同的 “視角” 洞察資料。 -並不是所有的系統都在其架構中明確區分**記錄系統**和**衍生資料系統**,但是這是一種有用的區分方式,因為它明確了系統中的資料流:系統的哪一部分具有哪些輸入和哪些輸出,以及它們如何相互依賴。 +並不是所有的系統都在其架構中明確區分 **記錄系統** 和 **衍生資料系統**,但是這是一種有用的區分方式,因為它明確了系統中的資料流:系統的哪一部分具有哪些輸入和哪些輸出,以及它們如何相互依賴。 大多數資料庫,儲存引擎和查詢語言,本質上既不是記錄系統也不是衍生系統。資料庫只是一個工具:如何使用它取決於你自己。**記錄系統和衍生資料系統之間的區別不在於工具,而在於應用程式中的使用方式。** @@ -28,7 +28,7 @@ ## 章節概述 -我們將從[第十章](ch10.md)開始,研究例如MapReduce這樣 **面向批處理(batch-oriented)** 的資料流系統。對於建設大規模資料系統,我們將看到,它們提供了優秀的工具和思想。[第十一章](ch11.md)將把這些思想應用到 **流式資料(data streams)** 中,使我們能用更低的延遲完成同樣的任務。[第十二章](ch12.md)將對本書進行總結,探討如何使用這些工具來構建可靠,可伸縮和可維護的應用。 +我們將從 [第十章](ch10.md) 開始,研究例如 MapReduce 這樣 **面向批處理(batch-oriented)** 的資料流系統。對於建設大規模資料系統,我們將看到,它們提供了優秀的工具和思想。[第十一章](ch11.md) 將把這些思想應用到 **流式資料(data streams)** 中,使我們能用更低的延遲完成同樣的任務。[第十二章](ch12.md) 將對本書進行總結,探討如何使用這些工具來構建可靠,可伸縮和可維護的應用。 ## 索引 diff --git a/zh-tw/preface.md b/zh-tw/preface.md index 1e533a5c..f80fc9ac 100644 --- a/zh-tw/preface.md +++ b/zh-tw/preface.md @@ -1,6 +1,6 @@ # 序言 -如果近幾年從業於軟體工程,特別是伺服器端和後端系統開發,那麼你很有可能已經被大量關於資料儲存和處理的時髦詞彙轟炸過了: NoSQL!大資料!Web-Scale!分片!最終一致性!ACID! CAP定理!雲服務!MapReduce!實時! +如果近幾年從業於軟體工程,特別是伺服器端和後端系統開發,那麼你很有可能已經被大量關於資料儲存和處理的時髦詞彙轟炸過了: NoSQL!大資料!Web-Scale!分片!最終一致性!ACID! CAP 定理!雲服務!MapReduce!實時! 在最近十年中,我們看到了很多有趣的進展,關於資料庫,分散式系統,以及在此基礎上構建應用程式的方式。這些進展有著各種各樣的驅動力: @@ -32,11 +32,11 @@ 本書是為軟體工程師,軟體架構師,以及喜歡寫程式碼的技術經理準備的。如果你需要對所從事系統的架構做出決策 —— 例如你需要選擇解決某個特定問題的工具,並找出如何最好地使用這些工具,那麼這本書對你尤有價值。但即使你無法選擇你的工具,本書仍將幫助你更好地瞭解所使用工具的長處和短處。 -你應當具有一些開發Web應用或網路服務的經驗,且應當熟悉關係型資料庫和SQL。任何你瞭解的非關係型資料庫和其他與資料相關工具都會有所幫助,但不是必需的。對常見網路協議如TCP和HTTP的大概理解是有幫助的。程式語言或框架的選擇對閱讀本書沒有任何不同影響。 +你應當具有一些開發 Web 應用或網路服務的經驗,且應當熟悉關係型資料庫和 SQL。任何你瞭解的非關係型資料庫和其他與資料相關工具都會有所幫助,但不是必需的。對常見網路協議如 TCP 和 HTTP 的大概理解是有幫助的。程式語言或框架的選擇對閱讀本書沒有任何不同影響。 如果以下任意一條對你為真,你會發現這本書很有價值: -* 你想了解如何使資料系統可伸縮,例如,支援擁有數百萬使用者的Web或移動應用。 +* 你想了解如何使資料系統可伸縮,例如,支援擁有數百萬使用者的 Web 或移動應用。 * 你需要提高應用程式的可用性(最大限度地減少停機時間),保持穩定執行。 * 你正在尋找使系統在長期執行過程易於維護的方法,即使系統規模增長,需求與技術也發生變化。 * 你對事物的運作方式有著天然的好奇心,並且希望知道一些主流網站和線上服務背後發生的事情。這本書打破了各種資料庫和資料處理系統的內幕,探索這些系統設計中的智慧是非常有趣的。 @@ -48,7 +48,7 @@ 本書並不會嘗試告訴讀者如何安裝或使用特定的軟體包或API,因為已經有大量文件給出了詳細的使用說明。相反,我們會討論資料系統的基石——各種原則與利弊權衡,並探討了不同產品所做出的不同設計決策。 -在電子書中包含了線上資源全文的連結。所有連結在出版時都進行了驗證,但不幸的是,由於網路的自然規律,連結往往會頻繁地破損。如果你遇到連結斷開的情況,或者正在閱讀本書的列印副本,可以使用搜索引擎查詢參考文獻。對於學術論文,你可以在Google學術中搜索標題,查詢可以公開獲取的PDF檔案。或者,你也可以在 https://github.com/ept/ddia-references 中找到所有的參考資料,我們在那兒維護最新的連結。 +在電子書中包含了線上資源全文的連結。所有連結在出版時都進行了驗證,但不幸的是,由於網路的自然規律,連結往往會頻繁地破損。如果你遇到連結斷開的情況,或者正在閱讀本書的列印副本,可以使用搜索引擎查詢參考文獻。對於學術論文,你可以在 Google 學術中搜索標題,查詢可以公開獲取的 PDF 檔案。或者,你也可以在 https://github.com/ept/ddia-references 中找到所有的參考資料,我們在那兒維護最新的連結。 我們主要關注的是資料系統的**架構(architecture)**,以及它們被整合到資料密集型應用中的方式。本書沒有足夠的空間覆蓋部署、運維、安全、管理等領域 —— 這些都是複雜而重要的主題,僅僅在本書中用粗略的註解討論這些對它們很不公平。每個領域都值得用單獨的書去講。 @@ -89,8 +89,8 @@ For more information, please visit http://oreilly.com/safari. 更多人透過審閱草稿並提供反饋意見在本書的創作過程中做出了無價的貢獻。我要特別感謝Raul Agepati, Tyler Akidau, Mattias Andersson, Sasha Baranov, Veena Basavaraj, David Beyer, Jim Brikman, Paul Carey, Raul Castro Fernandez, Joseph Chow, Derek Elkins, Sam Elliott, Alexander Gallego, Mark Grover, Stu Halloway, Heidi Howard, Nicola Kleppmann, Stefan Kruppa, Bjorn Madsen, Sander Mak, Stefan Podkowinski, Phil Potter, Hamid Ramazani, Sam Stokes, 以及Ben Summers。當然對於本書中的任何遺留錯誤或難以接受的見解,我都承擔全部責任。 -為了幫助這本書落地,並且耐心地處理我緩慢的寫作和不尋常的要求,我要對編輯Marie Beaugureau,Mike Loukides,Ann Spencer和O'Reilly的所有團隊表示感謝。我要感謝Rachel Head幫我找到了合適的術語。我要感謝Alastair Beresford,Susan Goodhue,Neha Narkhede和Kevin Scott,在其他工作事務之外給了我充分地創作時間和自由。 +為了幫助這本書落地,並且耐心地處理我緩慢的寫作和不尋常的要求,我要對編輯 Marie Beaugureau,Mike Loukides,Ann Spencer 和 O'Reilly 的所有團隊表示感謝。我要感謝 Rachel Head 幫我找到了合適的術語。我要感謝 Alastair Beresford,Susan Goodhue,Neha Narkhede 和 Kevin Scott,在其他工作事務之外給了我充分地創作時間和自由。 -特別感謝Shabbir Diwan和Edie Freedman,他們非常用心地為各章配了地圖。他們提出了不落俗套的靈感,創作了這些地圖,美麗而引人入勝,真是太棒了。 +特別感謝 Shabbir Diwan 和 Edie Freedman,他們非常用心地為各章配了地圖。他們提出了不落俗套的靈感,創作了這些地圖,美麗而引人入勝,真是太棒了。 最後我要表達對家人和朋友們的愛,沒有他們,我將無法走完這個將近四年的寫作歷程。你們是最棒的。 \ No newline at end of file