文章目录
  1. 1. 嵌入数据模型
  2. 2. 引用数据模型
  3. 3. 1:1
  4. 4. 1:N
    1. 4.1. N比较小
    2. 4.2. N比较大,
    3. 4.3. N非常大
    4. 4.4. 总结
  5. 5. N:N
    1. 5.1. 手动引用(Manual References)
    2. 5.2. DBRefs
  6. 6. Tips

MongoDB与关系型数据库的建模还是有许多不同,因为MongoDB支持内嵌对象和数组类型。MongoDB建模有两种方式,一种是内嵌(Embed),另一种是引用(Link)。那么何时Embed何时Link呢?那得看两个实体之间的关系是什么类型。

嵌入数据模型

嵌入数据模型是非正常(denormalized)的数据模型。嵌入数据模型,可以存相关的信息到同一个记录,用很少的操作就能完成查询,更新。以下场景使用嵌入数据模型:

  1. 对象之间是包含关系,1对1关系
  2. 对象之间是1对多关系

通常嵌入方式可以通过比较好的读性能,也可以完成原子性,但是文档的增长会成为一个性能问题。而且需要小于BSON文档的最大值。

嵌入文档的访问可以通过.(点)符号来实现。

引用数据模型

引用数据模型有一下好处:

  1. 当嵌入模型有重复数据出现,用引用模型可以减少重复,但是不能提供读性能
  2. 可以表示多对多关系
  3. 表示巨大的多次数据集

引用类型需要多趟执行才能获得结果这个是最大的缺点。

1:1

使用内嵌模型,就是将子文档嵌入在父文档中,如果使用引用类型,那么会需要多趟查询才能得到结果。如:

{
    _id: <ObjectId1>,
    username: "123xyz",
    contact: {
                phone: "123-456-7890",
                email: "xyz@example.com"
             }
}

1:N

上面说1:N应该使用内嵌模型,一般情况下这个是对的,但是要考虑N的取值范围,有时候我们也可能要区别对待。

N比较小

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

{  
    name: 'Kate Monster',  
    id: '123-456-7890',  
    addresses : [     
                    { 
                      street: '123 Sesame St', 
                      city: 'Anytown', cc: 'USA' 
                    },
                    { 
                      street: '123 Avenue Q', 
                      city: 'New York', cc: 'USA' 
                    }  
                ]
}

这种建模的方式包含了显而易见的优点和缺点:

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

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

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

N比较大,

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

零部件(Part):

{
    _id: object('AAAA'),
    partno: '123-aff-456',
    name : '#4 grommet',    
    qty: 94,    
    cost: 0.94,    
    price: 3.99
}

产品(Product):

{
    name : 'left-handed smoke shifter', m
    anufacturer : 'Acme Corp', 
    catalog_number: 1234, 
    parts : [ 
        // array of references to Part documents 
        ObjectID('AAAA'), 
        // reference to the #4 grommet above 
        ObjectID('F17C'), 
        // reference to a different Part 
        ObjectID('D2AA'), 
        // etc 
    ]
}

这种建模方式的优缺点也非常明显:

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

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

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

N非常大

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

主机(host):

{
    _id : ObjectID('AAAB'),    
    name : 'goofy.example.com',    
    ipaddr : '127.66.66.66'
}

日志(logmsg):

{
    time : ISODate("2014-03-28T09:42:41.382Z"),    
    message : 'cpu is on fire!',    
    host: ObjectID('AAAB')       // Reference to the Host document
}

我们在logsmg中,存放对host的_id引用即可。

总结

  1. 一对很少且不需要单独访问内嵌内容的情况下可以使用内嵌多的一方。
  2. 一对多且多的一端内容因为各种理由需要单独存在的情况下可以通过数组的方式引用多的一方的。
  3. 一对非常多的情况下,请将一的那端引用嵌入进多的一端对象中。

N:N

使用引用模型,引用存在两种方式:

手动引用(Manual References)

User:

{
    _id: 'never',
    'name': 'never',
}

Post:

{
    _id: objectId('....'),
    'title''postA',
    'author': 'never'
}

Post中的author就是Manual References,如果想查询一篇文章的作者信息,首先在post集合找出那篇文章,然后在user集合查找出用户的全部信息。但是假如有这么一个场景:用户可以对图片,文章等各种资源评论,所有的评论都放在comment集合中,如果只是使用Manual References,就分不清楚评论到底是属于哪类资源了,图片?文章?。所以有了DBRef。

DBRefs

形式:

{ $ref : <value>, $id : <value>, $db : <value> }
//$ref:集合名称;$id:引用的id;$db:数据库名称,可选参数

可以看到DBRef的结构比Manual References的复杂,占用的空间大,但是功能也强大,如果要跨数据库连接,上面讲的评论集合的例子,都得需要使用DBRef,MongoDB提供了函数来解析DBRef,不用像Manual References需要自己手动写两次查询。

>db.post.insert({
    title: 'postA',
    author: {$ref: 'user', $id: 'never'}
})

或者:

> db.posts.insert({title:'postA',  
            authors:[new DBRef('authors',author._id)]})
//此处author需要以及在上面初始化过。

查询:

db.post.find({"title":"Hello Mongodb DBRef1"})[0].authors[0].fetch()   

DBRef就是从文档的一个属性指向另一个文档的指针。

Tips

  1. 优先考虑内嵌,除非有什么迫不得已的原因。
  2. 需要单独访问一个对象,那这个对象就不适合被内嵌到其他对象中
  3. 数组不应该无限制增长。如果many端有数百个文档对象就不要去内嵌他们可以采用引用ObjectID的方案;如果有数千个文档对象,那么就不要内嵌ObjectID的数组。该采取哪些方案取决于数组的大小
  4. 不要害怕应用层级别的join:如果索引建的正确并且通过投影条件限制返回的结果,那么应用层级别的join并不会比关系数据库中join开销大多
  5. 在进行反范式设计时请先确认读写比。一个几乎不更改只是读取的字段才适合冗余到其他对象中
  6. 在mongodb中如何对你的数据建模,取决于你的应用程序如何去访问它们。数据的结构要去适应你的程序的读写场景

了解更多请参考官方文档

文章目录
  1. 1. 嵌入数据模型
  2. 2. 引用数据模型
  3. 3. 1:1
  4. 4. 1:N
    1. 4.1. N比较小
    2. 4.2. N比较大,
    3. 4.3. N非常大
    4. 4.4. 总结
  5. 5. N:N
    1. 5.1. 手动引用(Manual References)
    2. 5.2. DBRefs
  6. 6. Tips