本系列文章翻译自《50 Tips and Tricks for MongoDB Developers》,暂时没有找到中文版,反正自己最近也在深入学习mongodb,所以正好拿来翻译一下。一方面加强自己学习的效果,另一方面让大家也一起来体验一下需要我们这些mongodb使用者需要注意的地方。

首先声明自己的英文水平不是太高,加之有些英文翻译成中文也找不到合适的词来表达,所以在文章中可能会出现英文原词,或者说有些地方的翻译会有些生硬,也就是说会出现直译的地方。翻译该书的主要目的是为大家学习探讨用的,如果有翻译不精准的地方,或者说有更加精准的翻译,还请大家指出,我会及时的更正的,在此先谢过各位了。

Tip#1.Duplicate data for speed,reference data for integrity

数据冗余是为了性能,引用数据是为了完整性。

被多个文档使用的数据,既可以直接嵌入文档,也可以在文档中引用数据。嵌入不一定就比引用好,反之,引用也不一定就比嵌入好。每一种都有自己的取舍,不论什么,你都应该选择适合你的应用程序的方式。

嵌入式的结构,可能会导致数据的不一致。假设你需要把图1.1中的fruit的值从苹果修改为鸭梨,你刚修改完food集合中的fruit值,这时候你的应用崩溃了,其他地方的fruit的值还是旧的值,这时候你的应用中就同时存在两种不同的fruit值。

 

 

 图1.1 嵌入式结构,fruit的值既存在于food集合,也存在于meals集合

 

不一致性也不是什么大问题,但“不是大问题”也是有级别的,这个级别依赖于你的用户需求。对于很多的应用,短时间内的不一致是可以接受的。假设一个用户修改了他的姓名,在几个小时内,他的旧帖子继续显示他的旧姓名,是可以接受的。如果是即使短时间的不一致性也不能接受的话,你就需要考虑使用引用式的结构了。

 图1.2 引用式结构,fruit的值只存在于food集合,meals集合存储fruit的id

 

 这需要权衡,你不能同时拥有最好的性能和确保及时的数据一致性。你必须决定哪一个对于你的应用来说更重要。

举个例子来说。

假设我们正在设计一个购物车的应用,设计在mongodb中存在订单信息,订单需要包含哪些信息呢?

引用式结构

  1. a product: 
  2.   "_id":productId, 
  3.   "name":name, 
  4.   "price":price 
  5. a order: 
  6.   "_id":orderId, 
  7.   "user":userInfo, 
  8.   "items":[ 
  9.     productId1, 
  10.     productId2 
  11.   ] 



在订单的item项中存在每一个productid,当需要显示订单内容的时候,首先查询order集合,然后根据productid查询product集合来获取相应的产品名称,没有办法做到只用一次查询就可以获取完整的订单信息。

如果产品信息被更新,所有引用该产品的地方,都会显示新的产品信息。引用式的结构会拖慢读数据的速度,但是在多个订单会有很好的一致性,多个文档能实现原子的变化(只需要修改引用的文档的信息)。

嵌入式结构

  1. a product: 
  2.   "_id":productId, 
  3.   "name":name, 
  4.   "price":price 
  5. a order: 
  6.   "_id":orderId, 
  7.   "user":userInfo, 
  8.   "items":[ 
  9.     { 
  10.       "_id":productId1, 
  11.       "name":name, 
  12.       "price":price 
  13.     }, 
  14.     { 
  15.       "_id":productId2, 
  16.       "name":name, 
  17.       "price":price 
  18.     } 
  19.   ] 



将产品信息嵌入到订单信息中,当需要显示订单的时候,只需要执行一次查询即可。如果产品的信息发生变化,而且我们想要将变化传递给订单的话,我们需要更新多个独立的订单。

嵌入式结构加快了读取的速度,但是一致性会降低,产品信息不能被原子的在多个文档中被修改。

决定使用嵌入式结构还是使用引用式结构,可以参考下面的因素:
 

  • 为很少发生变化的数据,每次都是再次读取,你是否愿意付出这样的代价?

你是愿意承担1000次读取带来的惩罚?大多数应用,读的压力要大于写的压力。这需要你仔细测试自己的比例。

你正在考虑的引用数据的变化频率如何?改变越少,越是赞成使用嵌入式的结构。引用很少改变的数据,例如名称,出生日期,存货标记和地址,是很不值的。

  • 一致性有多重要?

如果一致性很重要,你就应该使用引用式结构。例如,多个文档需要原子的查看数据的变化。如果我们正在设计一个交易系统,有价证券只能在特定的时间才可以进行交易,到达不可以进行交易的时间,我们需要立即锁定这些有价证券。这种事情在应用级别来操作可能更好,因为应用需要知道在什么时间锁定或者解锁。

在上面的这个订单应用中,一致性可能是有害的。假设我们想要给一个产品打折,20% off。我们不想更新已经存在的订单中的产品信息。这时候,我们需要的可能是一个快照,是下单时候的产品信息。

  • 是否需要更快的读取速度?

如果需要尽可能快的读取速度,我们应该使用嵌入式结构。实时系统应该更多的使用嵌入式结构。

 

上面的这个订单文档是一个很好的嵌入式结构的例子,改变产品信息的时候,我们不想改变订单的信息。在这里,引用式结构不能带给我们任何的好处。

在这个例子中,嵌入式结构是最佳的选择。

最后给大家一个地址,Your Coffee Shop Doesn't Use Two-Phase Commit,,里面的例子讲述了在真实的环境中,如何处理一致性问题,以及相关的系统改如何设计。