2.6 模式设计
2.6.1 Mongo式的模式设计
使用Mongo有很多种方式,你本能上可能会像使用关系型数据库一样去使用。当然这样也可以工作得很好,但却没能发挥出Mongo的真正威力。Monog是专门设计为富对象模型(rich object model)使用的。
例如:如果你建立了一个简单的在线商店并且把产品信息存储在关系型数据库中,那你可能会有两个像这样的表:
item
title | price | item_features |
sku
feature_name | feature_value |
你进行了范式处理因为不同的物品有不同的特征,这样你不用建立一个包含所有特征的表了。在Mongo中你也可以像上面那样建立两个集合,但像下面这样存储每种物品会更有效。
item : {
"title" : <title> ,
"price" : <price> ,
"sku" : <sku> ,
"features" : {
"optical zoom" : <value> ,
...
}
}
因为只要查询一个集合就能取得一件物品的所有信息,而这些信息都保存在磁盘上同一个地方,因此大大提高了查询的速度。如果你想插入或更新一种特征,如db.items.update( { sku : 123 } , { "$set" : { "features.zoom" : "5" } } ),也不必在磁盘上移动整个对象,因为Mongo为每个对象在磁盘上预留了空间来适应对象的增长。
2.6.2 嵌入VS.引用
下面这个图中有两个集合:students和courses。
student文档中嵌入了address文档和scores文档,scores文档的“for_course”字段的值是指向courses集合的文档的引用。如果是关系型数据库,需要把“scores”作为一个单独的表,然后在students表中建立一个指向“scores”的外键。所以Mongo模式设计中的一个关键问题就是“是值得为这个对象新建一个集合呢,还是把这个对象嵌入到其它的集合中”。在关系型数据库中为了范式的要求,每个子项都要建一个单独的表,但在Mongo中使用嵌入式对象更有效,所以你应该给出不使用嵌入式对象而单独建一个集合的理由。
为什么说引用要慢些呢,以上面的students集合为例,你想执行:
print( student.scores[0].for_course.name );
如果这是第一次访问scores[0],那些客户端必须执行:
student.scores[0].for_course = db.courses.findOne({_id:_course_id_to_find_}); //伪代码
所以每一次遍历引用都要对数据库进行一次这样的查询,即使所有的数据都在内存中。再考虑到从客户端到服务器端的种种延迟,这个时间也不会低。
有一些规则可以决定该用嵌入还是引用:
1. 第一个类对象,也就是处于顶层的,往往应该有自己的集合。
2. 排列项详情对象应该用嵌入。
3. 处于被包含关系的应该用嵌入。
4. 多对多的关系通常应该用引用。
5. 数据量小的集合可以放心地做成一个单独的集合,因为整个集合可以很快地cached。
6. 要想获得嵌入式对象的系统级视图会更困难一些。如上面的“Scores”如果不做成嵌入式对象可以更容易地查询出分数排名前100的学生。
7. 如果嵌入的是大对象,需要留意到BSON对象的4M大小限定(后面会讲到)。
8. 如果性能是关键就用嵌入。
下面是一些示例:
1. Customer/Order/Order Line-Item
cutomers和orders应该做成一个集合,line-items应该以数组的形式嵌入在order中。
2. 博客系统
posts应该是一个集合;author可以是一个单独的集合,如果只需记录作者的email地址也可以以字段的方式存在于posts中;comments应该做成嵌入的对象。