第五章 - 何时使用MongoDB

至此您应该对MongoDB有了足够的了解并且知道在现有系统中何处以及怎样应用它了。然而,新的存储技术不止一个,让人很容易就被这么多的可选方案搞得不知所措。

对我来说,虽然与MongoDB无关,但最重要的一点是再也不会依赖于某一单一的方案来处理数据了。当然,对很多项目,很可能是大多数项目而言,采用单一的解决方案有很明显的好处,甚至是明智的选择。然而这里要强调的是您并非必须使用不同的技术,而是您可以这样做。只有您知道引入新技术带来的好处会不会大于采用新技术所需的代价。

说了这些,我希望基于目前对MongoDB的介绍,您会把它当成一个通用的方案。前面多次提到,面向文档的数据库和关系数据库有很多的共同点。因此,与其对之避而不谈,不如干脆把MongoDB视为关系数据库的平行方案(alternative。译者:这里的意思是MongoDB与关系数据库的功能类似但又有差异,如果说关系数据库是台式机,可以把MongoDB理解为笔记本电脑,两者有相似处,但是又各有优势,且没有哪一个能替代另一个)。只要是Lucene可以加强关系数据库全文索引能力的场合,或者是Redis可以当作持久性键-值存储的地方,MongoDB都可以用来集中存储管理这些数据。

注意,我没有说MongoDB是关系数据库的替代方案, 而是平行方案。作为一个工具,MongoDB能做的很多其他的工具也可以做到。有些方面MongoDB做得好些,有些方面差些。那么我们现在就来做进一步的分析。

无模式

面向文档的数据库常见的一个卖点就是它是无模式的。这使得它比传统数据库的表更加灵活。我同意无模式是很不错的一个特性,但不是因为大多数人说的那些原因。

当人们谈到无模式时,神奇得就好像忽然间我们就可以把一堆毫不匹配的数据通通塞进去一样。确实有这么一些领域或是数据格式,如果用关系数据库来建模是很痛苦的,但我认为这些只是一些不常见的边缘情况。无模式是很酷,不过系统的大部分数据都将是有着良好结构的。偶尔使用不匹配是会很方便,尤其是在引入新功能的时候。可是事实上也可以考虑一下添加一个可以为空的列,这样问题就解决了。

我认为无模式设计的真正益处在于不需要过多的设置,以及与面向对象编程语言结合使用的时候更少的阻力。这一点在使用静态语言的时候更是如此。我曾在C#和Ruby上使用过MongoDB,其间的差别不是一般的大。Ruby的动态特性还有广受欢迎的ActiveRecord实现使得这门语言本身就已经降低了面向关系和面向对象编程之间差异带来的难度。并不是说MongoDB和Ruby不是一个好的组合,相反的它们还真的很搭。我要说的是Ruby程序员眼中MongoDB带来的应该只是一些改进,而对于C#或者Java开发者来说,MongoDB带来的是处理数据方式的翻天覆地的转变。

以一个驱动开发者的角度来看,如果想要保存一个对象,可以把数据串行化为JSON(严格来说应该是BSON,不过JSON也足够接近了)再发送给MongoDB。不需要做属性或类型映射。作为终端开发者的您自然也直接得到了好处。

写操作

MongoDB擅长的一个特别角色是日志的记录。MongoDB有两点使得它的写操作非常快。第一,发出一条写命令后它会马上返回而不等待真正的写动作执行。第二,随着1.8中引入的日记功能(journaling),以及2.0中所做的优化加强,现在已经可以根据数据持久性来控制写操作的行为。这些设定的值,加上指定多少个服务器得到一份数据后才算是一次成功的写操作,在每次写的时候都是可以设置的。这些使得对写操作的性能以及数据的持久性的控制都上了一个档次。

除了上述性能上的因素,日志数据还是这样的一种数据格式:它们用无模式集合往往更有优势。最后,MongoDB还有一项技术叫做定量集合(capped collection)。目前为止,我们所创建的集合都是隐式创建的普通集合。我们可以用db.createCollection命令创建并标明它是给标量集合:

  1. //限制标量集合的大小为1MB
  2. db.createCollection('logs', {capped: true, size: 1048576})

当上面的定量集合增长到1MB的限制时后,旧的文档就会被自动删除。可以用max来限制文档的个数而不是整个集合的尺寸。定量集合有一些有意思的特性。比如说,你可以不断的更新文档,但是文档不会变大。同时,它会保存插入的顺序,因此没有需要添加额外的索引来实现基于时间的排序。

是时候说明这个了:如果需要知道写操作有没有出错(这和默认的射后不理的写行为相反)(译者:fire-and-forget,射后不理)只需要再发一个命令:db.getLastError()。多数的驱动都会把这种行为封装成一个安全的写操作,用{:safe => true}作为insert的第二个参数来声明。

持久性(Durability)

在1.8版之前,MongoDB是不支持单服务器的持久性的。也就是说,一个服务器宕机就会导致数据丢失。当时的解决方法就是在多台服务器上运行MongoDB(MongoDB支持复制)。新加入到1.8的一个重要特性就是日记(journaling)。打开这个功能需要在我们最早设置MongoDB时创建的mongodb.config文件中加入一行journal=true(如果想要立即生效,还需要重启服务器)。您应该是会想要打开这项功能的。(在以后的版本中将会默认打开)。不过,在有些情况下,您可能会要关闭日记以增加吞吐量,哪怕这样做存在风险。(需要指出的是有些应用对损失一些数据还是可以接受的)

关于持久性的话题只会在这里提及,因为为了克服MongoDB缺乏单服务器持久性的弊病人们已经做了大量的工作。这段话还可能会出现在以后的Google搜索结果中。您看到的那些说MongoDB这个缺点的信息都是过时了的。

全文搜索

真正的全文搜索功能希望能够在将来的MongoDB版本中实现。有了它对数组的支持,基本的全文搜索应该是很容易实现的。至于更高级一点的功能,就需要依仗像Lucene/Solr之类的方案了。当然,这一点上其他很多关系数据库也是一样的。

事务(transaction)

MongoDB是不支持事务的。不过它有两个替代的方案,其中一个很不错但是不怎么有人用,另外一个很麻烦同时又很灵活。

第一个就是MongoDB的众多原子操作。只要它们能够解决你的问题,都很好用。之前已经介绍过一些简单的诸如$inc$set。还有像findAndModify这样的原子操作,可以更新或是删除一个文档并返回修改过后的文档,且所有动作在一个原子操作内完成。

当原子操作不能满足要求时,可以退而尝试第二种方案:两阶段提交。两阶段提交之于事务就好比手工解引用(dereference,译者:关于这个词的翻译有很多种,用引、提领、解引用等,大家知道是什么意思就好。)之于连接。这是一个代码中实现的独立于存储系统的方案。两阶段提交其实在关系数据库中有普遍的应用,用以在多个数据库之间实现事务。MongoDB的网站有一个例子演示了最常见的场合(资金转账)。其主要思想就是把事务的状态储存在需要更新的文档中,并手工一步一步完成初始化-等待-提交或是回滚的每一个步骤。

MongoDB对嵌套文档以及无模式的支持使得两阶段提交稍微不那么痛苦了,不过这依旧不是一个很好的流程,尤其是当您刚刚开始学/用它的时候。

数据处理

MongoDB依靠MapReduce来完成大部分的数据处理工作。它有一些基本的聚合能力,尽管如此,无论在何种情况下您还是应该使用MapReduce。下一章我们将详细讨论MapReduce。现在把它当成是一个非常强大的工具,另外一种实现group by的方法。(这样说事实上低估了MapReduce)MapReduce的一个长处是它可以并行地处理大量的数据。可是MongoDB的实现却依赖于单线程的JavaScript。这又意味着什么呢?意味着如果是处理大量的数据,很可能还是需要用其他的工具,比如说Hadoop。幸好,这两个系统很好的实现了互补,且看这个MongoDB的Hadoop适配器

当然,并行处理数据也不是关系数据库所擅长的。现在已经有计划在将来的MongoDB版本中加强处理非常大的数据的能力。

地理空间

MongoDB的另外一个很强大的功能就是它对地理信息索引功能的支持。这个功能允许把X和Y坐标储存在文档中,然后可以用$near查找文档中靠近某个坐标的点,或是用$within找出位于某个矩形或是一个圆形中的点。这个功能可以用一些可视的例子来演示,如果您想了解多一些,我邀请您来试试这个5分钟交互式地理空间教程

成熟度与可用工具

你应该很可能早已经知道了答案,不过MongoDB明显比大多数的关系数据库要年轻。这个当然是您需要考虑的。究竟这个因素有多重要取决于您在做的是什么系统以及将怎样实现它。无论怎样,一个客观的评价是不会忽略这一事实:MongoDB很年轻而且周边可用的工具也不是很好用(虽然很多非常成熟的关系数据库可用的工具也很糟糕!)例如,不能支持十进制浮点数对货币数据系统来说就是一个很明显的问题(虽然不一定是致命的缺陷)

正面点来看,MongoDB为很多语言提供了驱动,它的协议很现代简约,开发速度非常快。有很多对不成熟的工具心存疑虑的公司都将MongoDB用在了自己已经发布的产品中。这些疑虑当然是有根据的,可是短时间后都已经成为了历史。

本章小结

本章要传递的信息是大多数情况下MongoDB都可以替代关系数据库。它更简单更直接,更快而且对应用程序开发者的约束更少。MongoDB不支持事务确实是一个缺点也值得慎重考虑。但是当人们问道在新数据存储阵营中MongoDB是怎样的一种技术?时,答案很简单:它很中庸。(译者:这里的意思是,作为NoSQL方案的一种,MongoDB显得更通用一些,不像Lucene那样专为搜索而生,也不像结构严格的Redis。)