想象一下这样的世界:数据库使用是如此的简单,以至于你忘记了正在使用它。再想象一下这样的世界:不需要任何复杂配置或设置,数据库仍然能够快速运行,并且具有良好的扩展性。想一下,如何可以只关注gg于手上的任务,完成它,并可以按时下班。这听起来有点神奇,但是MongoDB承诺帮助你完成所有这些事情(甚至更多)。
MongoDB(源自单词humongous)是一种相对较新的数据库,它没有表、模式、SQL或行的概念。它没有事务、ACID兼容性、连接、外键或其他许多容易在凌晨引起问题的特性。简单地说,MongoDB是一个非常特别的数据库,它不同于你之前所使用的数据库,特别是关系数据库管理系统(Relational DataBase Management System,RDBMS)。事实上,当你知道MongoDB缺少对这些所谓的“标准”特性的支持时,你甚至可能已经在摇头了。
不要担心!在接下来的内容中,你将学习MongoDB的背景和指导原则,以及MongoDB团队这样做的原因。我们也将浏览MongoDB的特性列表,并提供足够的细节,从而保证你会完全沉迷于本书其余部分的话题中。
首先我们将了解创建MongoDB背后的哲学和理念,以及一些有趣的和有争议的设计决策。我们将学习面向文档数据库的概念、文档的组织方式以及它们的优缺点。我们还将学习JSON以及如何在MongoDB中应用JSON。最后,我们将学习一些MongoDB最引人注目的特性。
1.1 了解MongoDB哲学
如同所有的项目一样,MongoDB有一套自己的设计哲学用于帮助指导开发。本节内容将介绍一些MongoDB数据库的基本原则。
1.1.1 使用正确的工具处理正确的工作
MongoDB中最重要的哲学概念是:一鞋难合众人脚。在过去的许多年中,传统的关系(SQL)数据库(MongoDB是面向文档的数据库)一直被用于存储所有类型的数据。无论该数据是否符合关系模型(被用在所有的RDBMS数据库中,例如MySQL、PostgresSQL、SQLite、Oracle、SQLServer等)都无所谓;无论如何,数据都将被填充到数据库中。一般来说,部分原因是因为读取和修改数据库相比操作文件系统更加简单(和安全)。如果选择一本PHP方面的书籍,例如PHP for Absolute Beginners,by Jason Lengstorf (Apress,2009),它将会教你使用数据库存储信息,而不是文件系统。这样做只是因为它更简单。在使用数据库存储信息的时候,开发者必须一直遵守它的工作流程。很明显,我们并未按照数据库原有的意图使用它。任何尝试在数据库中存储复杂数据、创建5张表,然后将它们组合在一起的开发者,都会明白我们在讲什么。
MongoDB团队决定他们不会创建另一个试图为所有人做所有事情的数据库。相反,该团队希望创建一个只用于处理文档的数据库,而不是行,并且它的速度要快,还要具有强大的扩展性和易用性。为了实现这个目标,他们不得不忽略一些特性,这意味着MongoDB在某些特定的情况下并不是最理想的选择。例如,它缺少事务支持,意味着无法使用MongoDB编写财务应用。也就是说,MongoDB可能对于之前提到的部分应用(例如存储复杂数据)是非常合适的。不过这不是个问题,因为你完全可以在财务模块中使用传统的RDBMS,而使用MongoDB存储文档。这样的混合解决方案非常常见,并且一些产品级应用(例如New York Times网站)已经在这样做了。
一旦适应MongoDB可能无法解决所有问题的理念之后,你会发现对于某些问题,MongoDB可以完美地解决它们,例如分析(例如网站中使用的实时Google Analytics)和复杂数据结构(例如博客文章和评论)。如果你仍然无法接受MongoDB是一个正式的数据库工具这个观点,那么请提前跳到1.3节“了解特性列表”,该部分内容将展示MongoDB的一些强大特性。
注意:
缺少事务和其他传统数据库特性并不意味着MongoDB不稳定,或者不能用于管理重要数据。
MongoDB设计背后的另一个关键概念是:数据库应该一直具有多个副本。如果单个数据库实例出现问题,那么它可以轻松地通过另一个服务器恢复到正常状态。因为MongoDB的目标是尽可能地快,所以它采取了一些捷径,导致它难以从系统崩溃中恢复。开发者认为最严重的系统崩溃可能就是从服务中移除一台计算机;这意味着即使数据库完全恢复了,也无法正常使用。记住:MongoDB不会尝试为所有人完成所有事情。但对于许多目的(例如构建Web应用),MongoDB是一个能够实现解决方案的完美工具。
现在你应该已经明白了MongoDB的起源。它不会尝试在所有方面都表现完美,也乐于承认它不会适用于所有人。不过,对于选择使用它的开发者,MongoDB提供了一个功能丰富的面向文档数据库,并且对运行速度和扩展性做了优化。它也几乎可以运行在任何目标上。MongoDB的网站上包含了可运行在Linux、Mac OS、Windows和Solaris中的安装文件。
MongoDB成功实现了这些目标,因此使用MongoDB有点像梦幻一样(至少对于我们来说)。不必担心如何将数据压缩到一张表中,只需要将数据组合在一起,然后将数据传递给MongoDB。考虑一个真实的例子。本书的合作者Peter Membrey最近开发了一个应用,用于存储一组eBay搜索结果。搜索结果的数量是不固定的(最多100个),因此他需要一种简单的方式在数据库中将用户和搜索结果关联起来。
Peter曾尝试使用MySQL,他不得不设计出一张表用于存储数据,并编写相应代码存储他的结果,然后再编写代码将结果组合在一起。这是一个相当常见的场景,大多数开发者在开发中经常会遇到。通常,我们不得不这样做;不过,对于该项目,他决定使用MongoDB,因此事情就变得有点不同了。
具体地说,他添加了下面这样两行代码:
request['ebay_results'] = ebay_results_array
collection.save(request)
在本例中,request是Peter的文档,ebay_result是键,而ebay_result_array包含来自eBay的搜索结果。第二行保存了修改后的数据。将来当他访问该文档时,他将获得与之前格式完全相同的数据。他不需要任何SQL;也不需要执行任何会话;更不需要创建任何新表或编写任何特殊代码——MongoDB就可以完成工作。他最终轻松地完成了工作,并按时回家。
1.1.2 天生缺少对事务的支持
MongoDB开发者做出的另一个重要设计决定是:该数据库将不会包含事务的语义(用于保证数据一致性和存储的元素)。这是根据MongoDB的目标——简单、快速和可扩展做出的权衡之计。一旦移除这些重量级特性,数据库就可以更轻松地实现水平扩展。
通常在使用传统的RDBMS时,如果需要提高性能,就要购买更大、更快的机器。这是一种垂直扩展的方式,但只能做到这种程度。如果使用了水平扩展,就不需要使用一台大型机器,相反可以使用许多稍弱一些的小机器。从历史上讲,这样的服务器集群非常有利于负载均衡网站,但因为数据库的内部设计限制,这种方式一直存在着问题。
你可能会认为缺少事务的支持将构成致命缺陷;不过,许多人都忘了一种在MySQL中最流行的表类型(MYISAM——这也恰好是默认的表类型)也不支持事务。但这个事实并未阻止MySQL在之前的10年中变成并保持着主流开源数据库的地位。与开发解决方案时的大多数选择一样,是否使用MongoDB取决于个人的偏好,以及它是否符合你的项目。
注意:
MongoDB在同时使用至少两台服务器时可以提供持久性,这也是为生产环境部署时推荐使用的基本配置。主服务器可以在复制服务器确认接收到数据后,再确认数据已被接受。
尽管目前单服务器的持久性是无法保证的,但在将来可能会改变,在目前这也是一个热门领域。
1.1.3 JSON和MongoDB
JSON(JavaScript Object Notation,JavaScript对象记号)不止是一种交换数据的方式,它也是一种存储数据的良好方式。RDBMS是高度结构化的,包含了多个文件(表)用于存储不同的数据。而MongoDB将在单个文档中存储所有的数据。MongoDB如同JSON一样,该模型为数据存储提供了丰富和高效的方式。而且,JSON可以高效地描述指定文档中的所有内容,所以不需要提前指定文档的结构。JSON是没有模式的(不需要模式),因为文档可以单独更新,或者独立于其他文档进行修改。另外,JSON还通过将相关数据存储在同一位置的方式提供了出色的性能。
实际上,MongoDB并未使用JSON存储数据,而使用由MongoDB团队开发的一种称为BSON(二进制JSON的英文简称)的开放数据格式。大多数情况下,使用BSON取代JSON并不会改变处理数据的方式。BSON通过使计算机更容易处理和搜索文档的方式,使MongoDB处理速度变得更快。BSON还添加了一些标准JSON不支持的特性,包括为处理二进制数据添加类型的能力。在本章的1.3.1节“使用面向文档存储(BSON)”中将深入讲解BSON。
RFC 4627中描述了JSON的初始规范,它由DouglasCrockford制订。JSON允许在简单的、可读文本格式中展现复杂的数据结构,使用它通常比阅读和理解XML要简单得多。如同XML一样,JSON被设计为一种在Web客户端(例如浏览器)和Web应用之间交换数据的方式。由于它描述对象的丰富方式和简单性,大多数开发者都选择它作为交换数据的格式。
你可能会好奇复杂数据结构到底意味着什么。历史上,CSV(Comma-SeparatedValue,逗号分隔值)曾用于交换数据(确实,这种方式在今天也仍然非常常见)。CSV是一种简单的文本格式,使用换行符分隔不同行,使用逗号分隔不同字段。例如下面的CSV文件:
Membrey, Peter, +852 1234 5678
Thielen, Wouter, +81 1234 5678
人们可以看懂这些信息,并且迅速找到其中传递的信息。问题是,第3列的数字是电话号码还是传真号码?它们甚至可能是寻呼机的号码。为了避免这种不确定性,CSV文件通常会提供头字段,添加在文件的第一行。下面的片段显示了之前样例的更多细节:
Lastname, Firstname, Phone Number
Membrey, Peter, +852 1234 5678
Thielen, Wouter, +81 1234 5678
这样就好多了。但现在假设CSV文件提到的某些人拥有多个电话号码。你可以添加另一个字段用作办公室电话号码,但当希望使用多个办公室电话号码时,你将遇到同样的问题。当希望处理多个邮件地址时也会遇到其他的问题。大多数人都会有多个邮件地址,这些地址无法被清晰地标注为家庭或工作地址。此时,CSV就暴露出了它的限制。CSV文件只适合用于存储扁平且没有重复值的数据。通常会出现这种情况,提供几个CSV文件,每个都包含不同的信息。然后将这些文件结合到一起(通常使用RDBMS)组成完整的数据。例如,一家大型的零售公司在每天结束时,可能会从它的每个商店收到CSV文件格式的销售数据。这些文件将被结合到一起,这样公司才能看到某天的销售状况。这个处理并不简单,随着需要的文件数目增加,出错的概率也会增大。
XML很大程度上解决了这个问题,但使用XML未免“杀鸡用牛刀”:可以工作,但更像是大题小做,因为XML是高度可扩展的。XML并非定义特定的数据格式,而是定义如何定义数据的格式。如果需要交换复杂并高度结构化的数据,XML是非常有用的;不过对于简单数据交换,它通常会导致过多的工作。事实上,该场景就是“XML地狱”这个词的来源。
JSON提供了一个折中方案。与CSV不同,它可以存储结构化的内容;但与XML也不同,JSON使内容易于理解和使用。下面使用JSON显示之前样例中的内容:
{
"firstname": "Peter",
"lastname": "Membrey",
"phone_numbers": [
"+852 1234 5678",
"+44 1234 565 555"
]
}
在这个版本的例子中,每个JSON对象(或文档)包含所有必需的信息。例如phone_numbers中包含一个含有不同数字的列表。该列表可以变成任意大小。还可以指定数字的具体类型,如下所示:
{
"firstname": "Peter",
"lastname": "Membrey",
"numbers": [
{
"phone": "+852 1234 5678"
},
{
"fax": "+44 1234 565 555"
}
]
}
该版本的样例有了一些改进。现在可以清楚地看到每个数字代表的含义。JSON具有丰富的表达力,尽管手动编写JSON也非常容易,但通常JSON都是由软件自动生成的。例如,Python中包含一个称为json的模块,用于将已有的Python对象自动转换为JSON格式。因为许多平台都支持和使用JSON,所以它是交换数据的理想选择。
在JSON中添加电话号码列表这样的元素时,事实上是在创建嵌入文档。在JSON中添加复杂的内容时其实就是在创建嵌入文档,例如添加列表(或数组,以使用JSON常用的术语)。例如,文档Person中可以内嵌几个Address文档。类似地,Invoice文档中可以内嵌多个LineItem文档。当然,内嵌的Address文档也可以内嵌包含了电话号码的文档。
在决定如何存储信息时,决定是否使用嵌入文档。这通常被称为模式设计。称之为模式设计似乎有点奇怪,因为MongoDB被认为是没有模式的数据库。不过,虽然MongoDB不强迫创建模式或增强已有的模式,你仍然需要思考如何将数据融合在一起。第3章将会深入进行讲解。
1.1.4 采用非关系的方式
改进关系型数据库性能的方式是非常直接的:购买更大更快的服务器。直到无法购买更快的服务器之前,这种方式都可以正常工作。而达到瓶颈时,唯一的选择就是扩展至两台服务器。这听起来似乎很容易,但它是大多数数据库的绊脚石。例如,MySQL和PostgresSQL都无法在两台服务器上同时运行单个数据库(两台服务器都能够读取或修改数据,通常被称为活跃/活跃集群)。尽管Oracle可以通过强大的实时应用集群(RealApplication Clusters,RAC)架构实现,但如果希望使用该解决方案,那么你可能会希望使用抵押贷款——实施基于RAC的解决方案需要多台服务器、共享存储和多个软件许可。
你可能会好奇为什么在两个数据库上部署活跃/活跃集群这么难。因为在查询数据库时,数据库需要找到所有相关的数据,并将它们关联在一起。RDBMS解决方案使用了许多巧妙的方式来提高性能,但它们都必须依赖于能否获得所有完整的数据。而这正是问题所在:当有一半数据在另一台服务器上时,这种方式是无法工作的。
当然,你可能只有一个小型的数据库,只是因为收到了大量的请求,所以希望通过共享负载的方式解决负载问题。遗憾的是,此时会出现另一个问题。需要保证写入第一台服务器的数据在第二台服务器上也是可用的。并且如果在两台服务器上同时更新数据,将会遇到另一个问题。例如,需要决定哪个更新才是正确的。可能遇到的另一个问题是:某人可能在第二台服务器上查询刚刚写入第一台服务器的信息,但是该信息尚未更新到第二台服务器。鉴于以上这些问题,你很容易就会明白为什么Oracle的解决方案这么贵——这些问题都很难解决。
MongoDB通过一种非常聪明的方式解决了这些活跃/活跃集群问题,并完全避免这些问题的发生。回想一下,MongoDB是以BSON文档格式存储数据的,所以数据是自包含的。尽管相似的数据文档被存储在一起,但各个文档之间并没有关系。这意味着,你需要的所有东西都在同一个地方。因为MongoDB查询将在文档中寻找特定的键和值,该信息可以轻松地被扩散到所有可用的服务器上。每台服务器都将检查该查询,并返回结果。这样,可扩展性和性能的提升几乎是线性的。作为额外好处,你不需要办理新的抵押贷款来实现该解决方案。
确实,MongoDB并不提供主/主复制,两台不同的服务器都可以接受这些请求。不过,它支持分片,允许将数据分散到多台机器中,每台机器都将负责更新数据集的不同部分。这种设计的优势在于,在某些解决方案支持两台主数据库时,MongoDB已经可以扩展到数百台机器,如同运行在两台机器上一样简单。
1.1.5 选择性能还是特性
性能是很重要的,不过MongoDB同样提供了一个巨大的特性集。我们已经讨论了一些MongoDB未实现的特性,你可能会有点怀疑MongoDB如何通过移除其他数据库中常见的特性来实现惊人的性能。不过,现在还有一些类似的数据库系统可用,它们也非常快,但同样功能也很有限,例如实现了键/值存储的数据库。
一个完美的例子就是memcached。该应用用于提供高速数据缓存,并且速度极快。使用它缓存网站内容时,它可以帮助加速应用许多倍。它被应用于非常大的网站,例如Facebook和LiveJournal。
美中不足的是,该应用有两个重大缺陷。首先,它是基于内存的数据库。如果系统断电,所有的数据都会丢失。其次,并不能真正用于数据搜索;只能请求特定的键。
这些听起来都是非常严重的限制;然而,要记住memcached被设计用来解决的问题。首先,memcached是一个数据缓存。它并不负责持久数据存储,只是为现有的数据库提供一个缓存层。在构建动态Web页面时,通常会请求非常特殊的数据(例如当前最热门的10篇文章)。这意味着,你可以向memcached请求该数据,不需要执行查询。如果cache过期或者没有数据,那么你将正常查询数据、构建数据,然后将数据存储在memcached中以便将来使用。
一旦接受这些限制,你会发现memcached通过实现一个非常有限的功能集,提供了卓越的性能。这样的性能是传统数据库所无法比拟的。事实上,memcached并不能替代RDBMS。最重要的是它也不应该用于替代数据库。
与memcached相比,MongoDB本身功能非常丰富。MongoDB为了具有竞争力,必须提供一组强大的功能,例如搜索特定文档的能力。它还必须能够将文档存储在硬盘中,这样在重启之后数据也不会丢失。幸运的是,对于大多数Web应用和许多其他类型的应用,MongoDB都提供了足够的功能,具有强大的竞争力。
如同memcached一样,MongoDB不是一个适合所有人的数据库。因此,要实现应用预期的目标,就必须作出一些权衡。
1.1.6 在任何地方均可运行数据库
MongoDB以C++编写,因此迁移相对容易,并且可以在任何位置运行该应用。目前,MongoDB网站提供了针对Linux、MacOS、Windows和Solaris的二进制包。在其他平台上,还为Fedora和CentOS提供了不同的官方版本。尽管推荐使用官网提供的二进制包,但你仍然可以下载源代码,构建自己的MongoDB。所有二进制包都提供了32位和64位版本。
警告:
32位版本的MongoDB数据库大小被限制为小于等于2GB,因为MongoDB内部使用内存映射文件来实现高性能。在32位系统中任何大于2GB的文件都需要一些特殊的处理,这样会降低处理速度,也会使应用代码变得复杂。官方关于该限制的观点是:64位环境很容易获得;因此,增加代码的复杂性并不是很好的权衡之计。64位版本的MongoDB可以实现所有的意图和目的,并且不含任何限制。
MongoDB适度的要求使它可以运行在高性能服务器或虚拟机上,甚至可以运行在基于云的应用中。通过保持事情简单,关注于速度和效率,无论将MongoDB部署在哪里,它都可以提供稳定的性能。
1.2 将所有组合在一起
在开始了解MongoDB的特性列表之前,首先需要了解一些基本的术语。开始使用MongoDB并不要求太多专业知识,许多特定于MongoDB的术语大致都可以被翻译成对应的RDMBS术语,这些术语你之前可能已经熟悉了。因此不要担心;我们将详细地解释每个术语。即使你不熟悉标准的数据库术语,也仍然可以轻松地明白它们。
1.2.1 生成或创建键
一个文档代表了MongoDB中的一个存储单元。在RDBMS中,存储单元将被称为行。不过,文档中包含的信息要远比行多,因为它们可以存储复杂的信息,例如列表、词典,甚至是词典的列表。在传统数据库中,它们的行是固定的,而MongoDB中的文档可以由任意数目的键和值组成(下一节中将进行详细讲解)。键只不过是一个标签,它大致相当于RDBMS中的列名。可以使用键引用文档中的数据。
在关系数据库中,必须能够通过某种方式唯一定位一条指定的记录;否则,将无法引用特定的行。为此,必须添加一个字段用于存储一个唯一值(称为主键),或者一组能够唯一定位指定行的字段(称为复合主键)。
出于相同的原因,MongoDB要求每个文档必须有唯一标识符;在MongoDB中,该标识符被称为_id。除非为该字段指定某个值,否则MongoDB将为你创建唯一值。即使是在已经成熟的RDBMS数据库世界中,也存在着是应该自己提供唯一键还是由数据库提供的分歧。最近,由数据库创建唯一键的方式已经变得更加流行。
这是因为由人创建的唯一数字,例如汽车注册号码,有不断改变的坏习惯。例如,在2001年,英国实施了一套新的牌照系统,与之前的系统完全不同。碰巧,MongoDB也可以完美地解决这个问题;不过,问题是如果使用车牌号码作为主键,就需要认真思考是否存在什么问题。当ISBN(International Standard Book Number,国际标准书号)从10位数字升级到13位时,也出现了类似的情况。
之前,使用MongoDB的大多数开发者似乎更喜欢创建自己的唯一键,由自己来维护键的唯一性。然而,现在人们更愿意使用MongoDB创建的默认ID值。不过,在使用RDBMS数据库时,选择哪种方式更多地取决于个人偏好。我们更愿意使用数据库提供的值,因为这意味着我们可以保证键是唯一的,并且是独立的。如前所述,其他人更愿意提供自己的键。
最终,你必须决定哪种方式更适合自己。如果有信心保证自己的键一定是唯一的(并且可能不会改变),那么就可以使用。如果不确定键的唯一性或者不希望担心这件事情,那么最好还是使用MongoDB提供的默认键。
1.2.2 使用键和值
文档由键和值组成。下面是之前章节中讨论过的一个样例:
{
"firstname":"Peter",
"lastname":"Membrey",
"phone_numbers":[
"+8521234 5678",
"+44 1234565 555"
]
}
键和值总是成对出现的。与RDBMS不同,RDMBS中的所有字段必须有值,即使值是NULL(有些矛盾的是,NULL意味着未知),MongoDB不要求文档必须含有特定的值。例如,如果不知道某人的电话号码,那就不添加。对于此事,一个通俗的类比是名片。如果你有传真号码,那就填入自己的传真号码;不过如果没有,也不用写入“传真号码:没有”。相反,不填写即可。如果MongoDB中不含某个键/值对,那它就被认为是不存在的。
1.2.3 实现集合
集合有点类似于表,但它们不那么死板。集合非常像一个贴有标签的盒子。在家里,你可能会有一个贴着“DVD”的盒子,用于放置DVD。这是合理的,但没有什么事情可以阻止你将CD或磁带放入该盒子中。在RDBMS中,表是严格定义的,只能够将指定的元素放入表中。在MongoDB中,集合就是一组类似元素的集合。其中的元素不必相似(MongoDB本质上是非常灵活的);不过,当我们开始学习索引和高级查询时,就会看到在集合中放置类似元素的优点。
你可以在一个集合中混合各种不同的元素,但几乎没有必要这么做。创建一个称为媒体的集合,那么家里所有的DVD、CD和磁带都应该放在其中。毕竟,这些元素有着共同的特性,例如艺术家名称、发布日期和内容。换句话说,是否应该将特定的文档存储在相同的集合中取决于应用的需求。至于性能方面,创建多个集合并不比只使用一个集合慢。记住:MongoDB的目标是帮助你让生活变得轻松,所以你应该按照自己感觉正确的方式去实现。
最后但并非最不重要的是,集合可以按需求即时创建。尤其是,在第一次尝试保存文档时,MongoDB将创建引用它的集合。这意味着可以按照需求即时创建集合(但并不是应该这么做)。因为MongoDB也允许动态地创建索引和执行其他数据库级别命令,所以可以利用该特性构建出一些非常动态的应用。
1.2.4 了解数据库
可能理解MongoDB中数据库的最简单方式就是将它看作一个集合的集合。如同集合一样,数据库可以按需创建。这意味着为每个客户创建一个数据库是很简单的,你的应用代码就可以做到。除了MongoDB,也可以在数据库中这样做;不过,在MongoDB中以这种方式创建数据库是非常自然的。也就是说,只因为你可以通过这种方式创建数据库,并不意味着必须或应该这么做。同样,你拥有权力,如果希望执行它的话。
1.3 了解特性列表
在了解MongoDB的定义以及它的特点之后,现在开始了解它的特性列表。在数据库网站www.mongodb.org/上有MongoDB的完整特性列表;一定要访问它们最新的列表。本章讲到的特性列表涉及一些MongoDB幕后的内容,事实上在使用MongoDB时并不需要了解所有的特性。换句话说,你可以自由挑选希望阅读的特性!
1.3.1 使用面向文档存储(BSON)
我们已经讨论过面向文档的设计,还简单接触了BSON。如之前学到的,JSON使它能够更容易地以文档真正的形式存储和获取文档,有效地避免了任何映射或特有转换代码的需求。事实上,该特性也使MongoDB变得更容易扩展。
BSON是一个开放的标准,在网址http://bsonspec.org/上可以找到它的规范。当人们听到BSON是JSON的二进制形式时,他们期望BSON占用的空间要比JSON少得多。不过,事实并不一定是这样的;在许多情况下,BSON版本与相同的JSON相比要占用更多的空间。
你可能会想知道,到底为什么要使用BSON。毕竟,CouchDB(另一个强大的面向文档数据库)使用的就是纯JSON,因此值得怀疑到底需不需在BSON和JSON之间来回转换文档。
首先,要记住MongoDB的设计目标是快速,而不是节省空间。虽然这并不意味着MongoDB会浪费空间(它不会);不过,如果处理数据的速度更快(它确实是这样的),那么存储文档时的一点开销是完全可以接受的。简单地说,BSON更易于遍历(即浏览),遍历索引页非常快速。尽管比起JSON,BSON需要稍微多一些的硬盘空间,但这并不是问题,因为硬盘很便宜,而且MongoDB可跨机器扩展。这种情况下的折中方案还是很合理的:多占用一点硬盘空间,换来更好的查询和索引性能。
第二个关键的优点在于,BSON的使用很简单,并且易于将BSON数据转换为编程语言的原生数据格式。如果以纯JSON方式存储数据,那就需要添加一个相对高级别的转换。对于大量的编程语言(例如Python、Ruby、PHP、C、C++和C#),MongoDB都有对应的驱动,它们的工作方式也稍有不同。使用简单的二进制格式,可以在各种语言中快速构建原生数据结构,而不需要首先对JSON进行处理。这将使代码更简单和快速,而它们都是MongoDB的目标。
BSON也提供了对JSON的一些扩展。例如,通过BSON可以存储二进制数据,以及处理特定的数据类型。因此,BSON可以存储任何JSON文档,但有效的BSON文档可能不是有效的JSON。这并没有关系,因为每种语言都有自己的驱动,可完成数据和BSON之间的转换,而不需要使用JSON作为中间语言。
事实上,BSON并不是影响你是否使用MongoDB的关键因素。如同所有伟大的工具一样,MongoDB将静静地在后台工作,完成它应做的事情。除了可以使用图形工具查看数据,你将一直使用自己的本地语言进行开发,让驱动来担心如何将数据存储到MongoDB中。
1.3.2 支持动态查询
MongoDB支持动态查询,这意味着可以运行查询而不需要提前做出计划。这类似于在RDBMS中运行SQL查询。你可能会想知道为什么这也会作为特性列出来;因为这应该是所有数据库都支持的特性。
实际上不是。例如,CouchDB(通常被认为是MongoDB的最大“竞争对手”)并不支持动态查询,因为CouchDB提出了一种全新的思考数据的方式(诚然这令人兴奋)。传统的RDBMS中有静态数据和动态查询。这意味着数据的结构必须提前确定下来——必须先定义表,并且每一行都必须符合该结构。因为数据库提前知道数据的结构,所以它可以做出特定的假设和优化,从而使动态查询更快速。
CouchDB将这点发挥到了极致。作为一个面向文档的数据库,CouchDB是无模式的,所以数据也是动态的。然而,这里的新理念是:查询是静态的。也就是说,在使用查询之前,要提前定义它们。
这听起来似乎并不坏,因为许多查询都可以轻松提前定义。例如,一个允许你搜索图书的系统,可能也会让你通过ISBN搜索图书。在CouchDB中,你将创建一个包含所有文档ISBN的列表的索引。当查询ISBN时,该查询是非常快速的,因为它并不需要搜索任何数据。无论何时在系统中添加新的数据,CouchDB都将自动更新它的索引。
从技术上讲,可以在不创建索引的情况下查询CouchDB;不过,在这种情况下,CouchDB不得不在处理请求之前自己创建索引。如果系统中只有数百本书,那么不会有任何问题;不过,如果系统中包含成千上万本书,它将导致性能变差,因为每个查询都将创建一个索引(又一次)。为此,CouchDB不推荐在生产环境中使用动态查询,也就是未被预定义的查询。
CouchDB还允许为查询编写映射和规约函数。如果这听起来似乎有些麻烦,那么你一定是在一家好公司工作;CouchDB的学习过程稍微难一些。公平地讲,对于CouchDB,有经验的程序员可能可以快速地掌握它;不过,对于大多数人来说,学习过程都是很难的,他们可能不会使用该工具。
对于我们这些凡人来说,幸运的是MongoDB非常易于使用。本书将详细讲解如何使用MongoDB,不过这里有一个简单的版本:在MongoDB中,只需要提供希望匹配的部分文档即可,MongoDB将完成其他的部分。不过MongoDB可以做更多。例如,如果希望使用映射或规约函数,那么你会发现MongoDB已经支持了该功能。同时,你可以轻松地使用MongoDB,不必知道之前介绍的所有工具。
1.3.3 为文档创建索引
MongoDB对文档索引提供广泛的支持,这是一个在处理成千上万文档时,使用起来非常方便的特性。没有索引,MongoDB就必须按顺序查看所有的文档,检查它们是否符合查询。这就像是在向图书管理员索要特定的图书,他将在图书馆查看每一本书来寻找。在使用索引系统之后(图书馆倾向于使用DeweyDecimal系统),他可以轻松地找到该书所属的区域,然后快速地确定它是否在那片区域。
与图书馆图书不同,MongoDB中的所有文档都将会在_id键上自动创建索引。该键被认为是一个特殊的例子,因为你不能删除它;该索引用于保证每个值都是唯一的。使用该键的优点之一是:可以确定每个文档都是唯一可识别的,RDBMS并不会保证这一点。
当你创建自己的索引时,可以决定是否希望它们具有强制唯一性。如果决定要创建唯一索引,可以告诉MongoDB删除所有重复的数据。这可能是也可能不是你所希望的,因此在使用该选项之前要进行认真思考,因为你可能一不小心就删除了一半的数据。默认情况下,如果在一个具有重复值的键上创建唯一索引,将会返回一个错误。
在许多情况下,你会希望创建一个允许重复的索引。例如,如果应用是按照姓氏搜索的,那么在姓氏键上创建索引是合理的。当然,这里无法保证每个姓氏都是唯一的;并且在任何具有合理大小的数据库中,含有重复数据实际上是正常的。
不过,MongoDB的索引能力并不止于此。MongoDB还可以在内嵌文档上创建索引。例如,如果在地址键中存储了大量的地址,那么可以在ZIP或邮政编码上创建索引。这意味着,可以轻松地将基于任意邮政编码的文档提取出来,并且非常快速。
MongoDB另外还支持复合索引。在复合索引中,可以使用两个或多个键用于构建索引。例如,你可能需要创建一个包含了姓氏和名字标签的索引。这时搜索全名将是非常快速的,因为MongoDB可以快速地定位到姓氏,然后同样快速地定位到名字。
第10章将对索引进行深入讲解,MongoDB提供了所有索引相关的功能。
1.3.4 使用地理空间索引
索引中特别值得一提的一种就是地理空间索引。MongoDB 1.4中引入了这个全新的特殊索引技术。通过使用该特性可以索引基于位置的数据,从而处理从给定坐标开始的特定距离内有多少个元素这样的查询。
随着使用基于位置数据的Web应用的增加,该特性在日常开发中的作用越来越重要。尽管现在,地理空间索引的应用仍然不够广泛;不过,如果你需要它,那么你会很高兴MongoDB已经支持它。
1.3.5 分析查询
通过一个内置的分析工具可以显示出MongoDB如何找到返回的文档。这非常有用,因为在许多情况下,通过添加一个索引就可以提高查询的性能。如果现在有一个复杂的查询,并且不确定到底为什么运行速度这么慢,那么查询分析器可以为你提供极具价值的信息。第10章将对MongoDB分析器进行详细讲解。
1.3.6 就地更新信息
当数据库更新一行数据时(对于MongoDB来说更新的是文档),它有多种更新方式可供选择。许多数据库选择多版本并发控制(Multi-VersionConcurrency Control,MVCC)方式,这种方式允许多用户看到不同版本的数据。因为在给定的事务过程中,这种方式可以保证数据不会被另一个程序改变,所以它非常有用。
这种方式不利的一面在于数据库需要追踪数据的多个副本。例如,CouchDB提供了非常强大的版本控制,但这是以记录所有数据为代价的。这种方式虽然能够保证数据以一种健壮的方式存储,但也增加了复杂性并降低了性能。
而MongoDB将就地更新信息。这意味着(与CouchDB相比),MongoDB可以在数据所在的位置更新它们。这通常意味着不需要更新额外的空间,索引可以保持不变。
这种方法的另一个优点是,MongoDB将执行懒写入。写入内存或从内存读取是非常快速的,但写入磁盘就会慢上几千倍。这意味着要尽可能地限制从磁盘读写数据。在CouchDB中这是不可能的,因为程序需要保证每个文档都被快速地写入磁盘。这种方式保证数据将被安全地写入磁盘,但也会对性能产生巨大影响。
MongoDB只在必须的时候才向磁盘写入,频率大概是每秒一次左右。这意味着如果一秒内某个值被更新了许多次——如果将某个值用于页面计数或存活统计,这种情况并不少见——那么该值只会被写入一次,而不是CouchDB要求的几千次。
这种方式使MongoDB更快速,当然它也是权衡的结果。CouchDB可能更慢一些,但它保证了数据将被安全地存储在硬盘中。MongoDB就无法保证这一点,这就是为什么传统的RDBMS可能更适用于管理一些关键数据,例如计费或应收账款。
1.3.7 存储二进制数据
GridFS是MongoDB在数据库中存储二进制数据的解决方案。BSON支持在一个文档中存储最多4MB的二进制数据,这可能已经可以满足你的需求。例如,如果希望存储个人资料,比如图片或声音,那么4MB将超出你的需要。另一方面,如果希望存储电影剪辑、高品质音频剪辑或几百兆大小的文件,MongoDB仍然可以做到。
GridFS通过在files集合中存储文件的信息(称为元数据)来实现。数据本身被分成多块(称为信息块)存储在chunks集合中。这种方式使数据存储既简单又有扩展性;还使范围操作(例如获取文件的特定部分)变得简单。
通常来说,我们将通过编程语言对应的MongoDB驱动来使用GridFS,所以并不需要清楚GridFS的细节。与其他所有MongoDB中的特性一样,GridFS的目标也是快速和可扩展性。这意味着如果需要处理大数据文件,MongoDB同样能够胜任。
1.3.8 复制数据
在讨论MongoDB背后的指导原则时,我们提到过RDBMS数据能够为数据存储的安全提供某种保证,而这是MongoDB所不具备的。由于一些原因,MongoDB并未实现这些保证。第一,这些特性会降低数据库的速度。第二,它们将大大地增加程序的复杂性。第三,最常见的故障应该是硬件故障,这种情况下即使数据被安全地保存到硬盘中,数据也无法使用。
当然,这些原因都不意味着数据安全不重要。如果无法在需要的时候访问到数据,那就不会有人愿意使用MongoDB。刚开始,MongoDB提供了包含主从复制特性的安全网络,这种情况下只有一个数据库可以处于活跃状态,并且可以在任何时间写入,这种方式在RDBMS世界中相当常见。该特性已经被复制集替代,基本的主从复制已经被弃用,也不应该再使用。
复制集有一台主服务器(类似于主从复制中的主服务器),它将处理所有来自于客户端的请求。因为在指定的复制集中只有一台主服务器,它可以保证所有的写入都会被正确处理。当写入操作发生时,该操作也会被写入主服务器的“oplog”集合中。
oplog集合将被复制到辅助服务器(可能有许多),并帮助它们将数据更新到与主服务器一致的状态。一旦主服务器出现故障,辅助服务器中的某一台将会成为主服务器,并负责处理所有来自客户端的写入请求。
1.3.9 实施分片
对于涉及大规模部署的应用,自动分片可能是MongoDB最重要和最常用的特性。
在自动分片场景中,MongoDB将处理所有数据的分割和重组。它将保证数据进入正确的服务器,并以最高效的方式运行查询和重组结果。事实上,从开发者的角度来看,使用含有数百个分片的MongoDB数据库和使用单个MongoDB数据库并没有区别。该特性尚未正式应用在生产环境中;不过一旦就绪,它将从根本上提升MongoDB的可扩展性。
与此同时,如果刚刚开始构建第一个基于MongoDB的网站,那么可能单实例MongoDB就足够满足需求。如果希望构建下一个Facebook或Amazon,那么你会很高兴使用这么一门规模可以无限扩展的技术。本书的第12章将对分片进行详细讲解。
1.3.10 使用映射和归约函数
许多人在听到术语MapReduce时会感到脊柱发凉。而另一个极端,许多RDBMS对映射和归约函数的复杂性嗤之以鼻。它们对于某些人来说是可怕的,因为这些函数要求使用一种完全不同的方式来思考如何查询和排序数据,许多专业的程序员对映射和归约函数背后的概念也感到头疼。这些函数提供了一种极其强大的查询数据的方式。事实上,CouchDB只支持这种方式,这也是为什么它有着陡峭的学习曲线。
MongoDB并不要求使用映射和归约函数。事实上,MongoDB只依赖于简单的查询语法,这种语法与MySQL中使用的类似。不过,对于希望使用该功能的人,MongoDB也提供了对这些函数的支持。映射和归约函数以JavaScript编写并运行在服务器中。映射函数的工作是查找所有符合条件的文档。然后这些结果会被传递到归约函数,归约函数将处理这些数据。不过归约函数并不会返回一个文档的集合;而是返回一个新的文档,其中包含衍生出的信息。作为一般规则,如果你会在SQL中使用GROUPBY,那么在MongoDB中就应该会使用映射和归约函数。
注意:
不应该将MongoDB的映射和归约函数看做CouchDB所采用方式的粗糙模仿。如果愿意的话,可以使用MongoDB的映射和归约函数取代MongoDB自有的所有查询支持。
1.3.11 全新的聚合框架
MapReduce是一个非常强大的工具,但它有一个主要的缺点;不易于使用。许多数据库系统都被用于生成报表,在特定的SQL数据库中这种方式非常容易。如果希望对结果分组或者查找最大值和平均值,那么它可以轻松地表达出你的想法并获得查找结果。遗憾的是,在MapReduce中这并不容易,必须自己完成所有的事情。这通常意味着,对于简单的任务来说使用MapReduce存在着不必要的困难。
出于这个原因,MongoDBInc.(之前的10gen)添加了聚合框架。它基于管道,并允许依次处理查询的各个部分,然后将它们按顺序串联起来,获得要查询的结果。这在保持了MongoDB面向文档设计的优点之外,还提供了高性能。
所以如果需要使用强大的MapReduce,那么仍然可以使用它。如果只是希望完成一些简单的统计和数字运算,你一定会喜欢新的聚合框架。在第4和第6章将会详细讲解聚合框架和它的命令。
1.4 获取帮助
MongoDB有一个伟大的社区,而且核心开发者都非常活跃和平易近人,他们通常都非常乐于帮助社区的其他成员。MongoDB易于使用并拥有很棒的文档;另外,你不孤单,如果需要任何帮助,都可以轻松获得!
1.4.1 访问网站
寻找更新信息或帮助的第一个地方就是MongoDB网站(http://www.mongodb.org)。该网站将定时更新,包含MongoDB最新的优点。在该网站中,可以找到驱动、教程、样例、常见问题等。
1.4.2 与MongoDB开发者沟通
MongoDB开发者通常会使用Freenode网站(www.freenode.net)上的Internet Relay Chat (IRC),频道为#MongoDB。MongoDB的开发者位于纽约,但他们通常会在这个频道中一直聊到深夜。当然,开发者也需要一些休息时间(咖啡的作用只能坚持这么久!);幸运的是,还有来自于世界各地的知识渊博的MongoDB用户愿意伸出帮助之手。许多访问#MongoDB频道的人并不是专家;不过,其中的气氛非常友好,所以他们愿意待在这个频道中。随时可以加入#MongoDB频道与其他人交流,你可能会发现一些很棒的提示和技巧。即便你真的卡在某个地方,也能够很快回到正轨。
1.4.3 剪切和粘贴MongoDB代码
Pastie(http://pastie.org)并不是一个严格意义上的MongoDB站点;不过,在#MongoDB频道中待一会儿总会遇到它。在Pastie站点中可以剪切和粘贴(以此得名)一些输出或程序代码,然后放到网上供其他人查看。在IRC中,粘贴的多行文本可能是混乱的或难于阅读的。如果需要贴出一些代码(例如三行以上),那么应该访问http://pastie.org,粘出你的内容,然后将页面的链接粘贴到频道中。
1.4.4 在Google小组中寻找解决方案
MongoDB有一个称为mongodb-user的Google小组(http://groups.google.com/group/mongodb-user)。这个小组是咨询问题或寻找答案的好地方。也可以通过电子邮件与小组交互。与短暂的IRC交流不同,Google小组是一个非常长期的资源。如果真的希望参与到MongoDB社区中,加入小组将是一个很好的开始。
1.4.5 利用JIRA跟踪系统
MongoDB使用JIRA问题跟踪系统。可以访问跟踪网站http://jira.mongodb.org/,并且可以在网站提交遇到的问题或bug。将这样的问题报告到社区是一件真正的好事。当然也可以搜索之前已存在的问题,并且可以查看MongoDB的发展蓝图和下一版本的更新计划。
在尚未提交问题之前,可以先访问IRC聊天室。你很快会发现自己的问题是否是个新的问题,如果是,你将会明白如何报告它。
1.5 小结
本章对MongoDB的优点进行了全面介绍。我们了解了MongoDB创建和开发背后的哲学和指导原则,以及在实现这些理念时MongoDB开发者作出的权衡。另外,还了解了MongoDB中使用的一些关键术语,如何组合它们以及它们在SQL中对应的术语。
接着,我们了解了MongoDB提供的一些特性,包括如何使用和应该在哪里使用它们。最后,我们对社区和可以寻求帮助的地方做了快速浏览,你一定会需要它们的!
《MongoDB大数据处理权威指南(第2版)》试读电子书,免费提供,有需要的留下邮箱,一有空即发送给大家。 别忘啦顶哦!
购书地址:
互动:http://product.china-pub.com/3770736
亚马逊:http://www.amazon.cn/dp/B00QG1GIE4