MongoDB数据结构设计中6条重要的经验法则

很多初学者认为在MongoDB中针对一对多建模唯一的方案就是在父文档中内嵌一个数组子文档,但是这是不准确的。因为你可以在MongoDB内嵌一个文档不代表你就必须这么做。

当你设计一个MongoDB数据库结构,你需要先问自己一个在使用sql时不会考虑的问题:这个关系中集合的大小是什么样的规模?你需要意识到一对很少,一对许多,一对非常多,这些细微的区别。不同的情况下你的建模也将不同。

一对很少

一个人的地址为例,这时候使用内嵌文档是很合适,可以在person文档中嵌入数组地址文档:

< db.person.findOne()

{

name: ‘Kate Monster’,

ssn: ’123-456-7890′,

addresses : [

{ street: '123 Sesame St', city: 'Anytown', cc: 'USA' },

{ street: '123 Avenue Q', city: 'New York', cc: 'USA' }

]

}

这种设计拥有内嵌文档设计中所有的优缺点。最主要的优点就是不需要单独执行一条语句去获取内嵌的内容。最主要的缺点是你无法把这些内嵌文档当做单独的实体去访问。

一对多

以商品替换零件订货系统为例。每个商品有数百个可替换的零件,但是不会超过数千个。这个用例很适合使用间接引用-将零件的objectid作为数组存放在商品文档中(在这个例子中我使用更加易读的2字节的ObjectID,现实世界中他们可能是由12个字节组成的)。

< db.parts.findOne()

{

_id : ObjectID(‘AAAA’),

partno : ’123-aff-456′,

name : ‘#4 grommet’,

qty: 94,

cost: 0.94,

price: 3.99

}

< db.products.findOne()

{

name : ‘left-handed smoke shifter’,

manufacturer : ‘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

]

在获取特定产品中所有零件,需要一个应用层级别的join

< product = db.products.findOne({catalog_number: 1234});

< product_parts = db.parts.find({_id: { $in : product.parts } } ).toArray() ;

为了能快速的执行查询,必须确保products.catalog_number有索引。当然由于零件中parts._id一定是有索引的,所以这也会很高效。

这中引用的方式是对内嵌优缺点的补充。每个零件是个单独的文档,可以很容易的独立去搜索和更新他们。使用这种建模方式需要考虑的一个问题是需要一条单独的语句去获取零件的具体内容

这种建模方式中的零件部分可以被多个产品使用,所以在多对多时不需要一张单独的连接表。

一对很多

我们用一个收集不同机器日志的例子来讨论一对很多的问题。由于每个mongodb的文档有16M的大小限制,所以即使你是存储ObjectID也是不够的。我们可以使用很经典的处理方法“父级引用”—用一个文档存储主题,在每个日志文档中保存这个主机的ObjectID。

< db.hosts.findOne()

{

_id : ObjectID(‘AAAB’),

name : ‘goofy.example.com’,

ipaddr : ’127.66.66.66′

}

<db.logmsg.findOne()

{

time : ISODate(“2014-03-28T09:42:41.382Z”),

message : ‘cpu is on fire!’,

host: ObjectID(‘AAAB’) // Reference to the Host document

}

以下是个稍微不同的应用级别的join用来查找一台主机最近5000条的日志信息

< host = db.hosts.findOne({ipaddr : ’127.66.66.66′});

< last_5k_msg = db.logmsg.find({host: host._id}).sort({time : -1}).limit(5000).toArray()

所以,即使这种简单的讨论也有能察觉出mongobd的建模和关系模型建模的不同之处。你必须要注意一下两个因素:

一对多中的多是否需要一个单独的实体。

这个关系中集合的规模是一对很少,很多,还是非常多。

基于以上因素来决定采取一下三种建模的方式

一对很少且不需要单独访问内嵌内容的情况下可以使用内嵌多的一方的方案。

一对多且多的一段内容因为各种理由需要单独存在的情况下可以使用通过数组的方式引用多的一方的方案。

一对非常多的情况下,请将一的那端引用签入进多端的方案。

原文发布于微信公众号 - php(phpdaily)

原文发表时间:2015-11-27

本文参与腾讯云自媒体分享计划,欢迎正在阅读的你也加入,一起分享。

发表于

我来说两句

0 条评论
登录 后参与评论

相关文章

来自专栏佳爷的后花媛

如何自制条形码扫描器

在文章之前要先感谢我的老师黄小平先生,在制作这个扫描器期间,给了我们很大的帮助和指导. 先说下这个条形码扫描器的预期功能: 如名称所示,主要达到的功能就是实...

743
来自专栏杨建荣的学习笔记

和开发人员讨论一个业务需求和简单实现过程(r7笔记第49天)

前几天一个开发的同事来找我咨询一个问题,说是咨询,其实是开发的同事也不是非常清楚里面的逻辑,因为历史系统,历史原因,各种原因吧,所以我也是带着试试看的态度来帮助...

3144
来自专栏进击的程序猿

raft 系列解读(4) 之 etcd-raft学习

大多数Raft的实现都是整体设计,包括存储处理,消息序列化和网络传输,但是本raft库在实现的时候只实现了最核心的算法,换来了灵活性和性能,网络和disk IO...

794
来自专栏撸码那些事

【封装那些事】 泄露的封装

1162
来自专栏Golang语言社区

Go 的垃圾回收机制在实践中有哪些需要注意的地方?

之前回答问题的时候Go还处在1.1版本,到了1.2和1.3,Go的GC跟踪命令和GC内部实现已经有一些变化,并且根据评论中的反馈,这边一并做补充说明。 Go ...

4216
来自专栏流柯技术学院

JMeter性能测试中控制业务比例

性能测试混合场景中,我们需要组合多个业务操作到场景中来。 比如有一个论坛的业务分布如下: 发布新帖与回复帖子的比例为2:3, 那么我们在JMeter测试计划中如...

1113
来自专栏跨界架构师

如何一步一步用DDD设计一个电商网站(八)—— 会员价的集成

前面几篇已经实现了一个基本的购买+售价计算的过程,这次再让售价丰满一些,增加一个会员价的概念。会员价在现在的主流电商中,是一个不大常见的模式,其带来的问题是:

742
来自专栏哲学驱动设计

重构实践:体验interface的威力(二)

前一篇博客 写了在这次重构中,如何找到关键的概念,并将它提取成为接口。这样,重构的方案基本上就已经被确定了。这篇博客主要说一些有意义的细节: 抽象实现 ...

1788
来自专栏Python中文社区

基于Redis的Bloomfilter去重

专栏作者简介 九茶 Python工程师,目前居于广州。Github知名开源爬虫QQSpider和SinaSpider作者,经常会在CSDN上分享一些爬虫、数据等...

5378
来自专栏专注研发

【2018】笔试题笔记

3.对关键字{10,20,8,25,35,6,18,30,5,15,28}序列进行希尔排序,取增量d =5时,排序结果为( {6,18,8,5,15,10,...

964

扫码关注云+社区