MongoDB 一对多关系建模

本篇博客翻译自:

http://blog.mongodb.org/post/87200945828/6-rules-of-thumb-for-mongodb-schema-design-part-1?mkt_tok=3RkMMJWWfF9wsRonsq7Ldu%2FhmjTEU5z14uUsUKGxhokz2EFye%2BLIHETpodcMTcVnM7zYDBceEJhqyQJxPr3FLdcN0tJuRhTrCw%3D%3D

备注:本译文不是严格意义上的翻译,只是在基于对该原文的理解之上,尽可能表达清楚。如有疑问或不妥,请参考原文。


很多刚从传统SQL开发转向MongoDB开发的朋友都会问到一个问题:如何用MongoDB表达传统关系数据库中的一对多(1 to n)关系?

基于MongoDB丰富的表达力,我们不能说我们必须采用一个标准的方法来进行1 to n的建模。稍后我们从3个具体场景来展开讲解。


首先,我们将1 to n中的n进行场景细化。这个n究竟代表多大的量级呢?是几个到几十个?还是几个到几千个?还是成千上万个?

1) 1 to n(n代表好几个,或几十个,反正不太多)

比如每个Person会有多个Address。此种情况下,我们采用最简单的嵌入式文档来建模。

<div class="line" id="file-gistfile1-txt-LC2">{</div><div class="line" id="file-gistfile1-txt-LC3">  name: 'Kate Monster',</div><div class="line" id="file-gistfile1-txt-LC4">  id: '123-456-7890',</div><div class="line" id="file-gistfile1-txt-LC5">  addresses : [</div><div class="line" id="file-gistfile1-txt-LC6">     { street: '123 Sesame St', city: 'Anytown', cc: 'USA' },</div><div class="line" id="file-gistfile1-txt-LC7">     { street: '123 Avenue Q', city: 'New York', cc: 'USA' }</div><div class="line" id="file-gistfile1-txt-LC8">  ]</div><div class="line" id="file-gistfile1-txt-LC9">}</div>
这种建模的方式包含了显而易见的优点和缺点:

优点:你不需要执行单独的查询就可以获得某个Person的所有Address信息。

缺点:你无法像操作独立文档那样来操作Address信息。你必须首先操作(比如查询)Person文档后,才有可能继续操作Address。

在本实例中,我们不需要对Address进行独立的操作,且Address信息只有在关联到某一个具体Person后才有意义。所以结论是:采用这种embedded(嵌入式)建模是非常适合Person-Address场景的。


2)1 to n(n代表好些个,比如几十个,甚至几百个)可以考虑使用DBRef

比如产品(Product)和零部件(part),每个产品会有很多个零部件。这种场景下,我们可以采用引用方式来建模,如下:

<div class="line" id="file-gistfile1-txt-LC2">零部件(Part):</div><div class="line" id="file-gistfile1-txt-LC2">{</div><div class="line" id="file-gistfile1-txt-LC3">    _id : ObjectID('AAAA'),</div><div class="line" id="file-gistfile1-txt-LC4">    partno : '123-aff-456',</div><div class="line" id="file-gistfile1-txt-LC5">    name : '#4 grommet',</div><div class="line" id="file-gistfile1-txt-LC6">    qty: 94,</div><div class="line" id="file-gistfile1-txt-LC7">    cost: 0.94,</div><div class="line" id="file-gistfile1-txt-LC8">    price: 3.99</div><div class="line" id="file-gistfile1-txt-LC9">}</div><div class="line" id="file-gistfile1-txt-LC9">
</div><div class="line" id="file-gistfile1-txt-LC9">产品(Product):</div><div class="line" id="file-gistfile1-txt-LC9"><pre name="code" class="line-pre"><div class="line" id="file-gistfile1-txt-LC2">{</div><div class="line" id="file-gistfile1-txt-LC3">    name : 'left-handed smoke shifter',</div><div class="line" id="file-gistfile1-txt-LC4">    manufacturer : 'Acme Corp',</div><div class="line" id="file-gistfile1-txt-LC5">    catalog_number: 1234,</div><div class="line" id="file-gistfile1-txt-LC6">    parts : [     // array of references to Part documents</div><div class="line" id="file-gistfile1-txt-LC7">        ObjectID('AAAA'),    // reference to the #4 grommet above</div><div class="line" id="file-gistfile1-txt-LC8">        ObjectID('F17C'),    // reference to a different Part</div><div class="line" id="file-gistfile1-txt-LC9">        ObjectID('D2AA'),</div><div class="line" id="file-gistfile1-txt-LC10">        // etc</div><div class="line" id="file-gistfile1-txt-LC11">    ]</div><div class="line" id="file-gistfile1-txt-LC11">}</div>
 

首先每个part作为单独的文档存在。每个产品中包含一个数组类型字段(parts),这个数组中存放的是所有该产品包含的零部件的编号(_id主键)。当你需要根据某一个产品编号查询该产品包含的所有部件信息时,你可以执行以下操作:

<div class="line" id="file-gistfile1-txt-LC2">> product = db.products.findOne({catalog_number: 1234});</div><div class="line" id="file-gistfile1-txt-LC3">   // Fetch all the Parts that are linked to this Product</div><div class="line" id="file-gistfile1-txt-LC4">> product_parts = db.parts.find({_id: { $in : product.parts } } ).toArray() ;</div>
这种建模方式的优缺点也非常明显:

优点:部件是作为独立文档(document)存在的,你可以对某一部件进行独立的操作,比如查询或更新。

缺点:如上,你必须通过两次查询才能找到某一个产品所属的所有部件信息。

在本例中,这个缺点是可以接受的,本身实现起来也不难。而且,通过这种建模,你可以轻易的将1 to n扩展到n to n,即一个产品可以包含多个部件,同时一个部件也可以被多个产品所引用(即同一部件可以被多个产品使用)。


3)1 to n(这个n代表很大的数值,比如成千上万,甚至更大)

比如,每一个机器(host)会产生很大数量的日志信息(logmsg)。在这种情况下,如果你采用嵌入式建模,则一个host文档会非常庞大,从而轻易超过MongoDB的文档大小限制,所以不可行。如果你采用第二中方式建模,用数组来存放所有logmsg的_id值,这种方式同样不可行,因为当日止很多时,即使单单引用objectId也会轻易超过文档大小限制。所以此时,我们采用以下方式:

<div class="line" id="file-gistfile1-txt-LC1">机器(hosts):</div><div class="line" id="file-gistfile1-txt-LC2">{</div><div class="line" id="file-gistfile1-txt-LC3">    _id : ObjectID('AAAB'),</div><div class="line" id="file-gistfile1-txt-LC4">    name : 'goofy.example.com',</div><div class="line" id="file-gistfile1-txt-LC5">    ipaddr : '127.66.66.66'</div><div class="line" id="file-gistfile1-txt-LC6">}</div><div class="line" id="file-gistfile1-txt-LC7"> </div><div class="line" id="file-gistfile1-txt-LC8">日志(logmsg):</div><div class="line" id="file-gistfile1-txt-LC9">{</div><div class="line" id="file-gistfile1-txt-LC10">    time : ISODate("2014-03-28T09:42:41.382Z"),</div><div class="line" id="file-gistfile1-txt-LC11">    message : 'cpu is on fire!',</div><div class="line" id="file-gistfile1-txt-LC12">    host: ObjectID('AAAB')       // Reference to the Host document</div><div class="line" id="file-gistfile1-txt-LC13">}</div>
我们在logsmg中,存放对host的_id引用即可。


综上所述,在对1 to n关系建模时,我们需要考虑:

1)n代表的数量级很小,且n代表的实体不需要单独操作时,可以采用嵌入式建模。

2)n代表的数量级比较大,或者n代表的实体需要单独进行操作时,采用在1中用Array存放引用的方式建模。

3)n代表的数量级非常大时,我们没有选择,只能在n端添加一个引用到1端。

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值