MongoDB的数据建模
MongoDB是一种面向Document的NoSQL数据库,如果我们还是按照RDB的方式来思考MongoDB的数据建模,则不能有效地利用MongoDB的优势;然而,我们也不能因为Document的灵活性,就可以在设计之初放任自流。
适度的建模是非常有必要的,尤其对于相对复杂的关联关系。因为在MongoDB中,处理这种关联关系既可以使用Link,也可以使用Embedded。
我们要评价一种决策,不能将其与具体的上下文割裂开来做判断,那种单纯说A技术要比B技术好的做法,就像小孩子看卡通片里的人物只知道说谁是好人谁是坏人一般的幼稚。世界上没有一种完美至善的技术,关键还是要结合场景来看使用是否得法。
例如使用Embedded方式,就各有优缺点。举例来说,倘若我们采用Embedded方式将Addresses作为Person对象内部的数组:
{
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' }
]
}
当我们在查询Person的信息时,要获取其内嵌的属性细节,我们无需再执行多次查询。倘若我们改变一下领域场景,需要开发一个任务跟踪系统。如果我们将Tasks的信息嵌入到Person对象中,当我们面对以下需求:
- 显示所有明天到期的任务
- 显示所有未完成的任务
采用这种Embedded就不那么令人愉快了。
如果采用Link方式,情况就完全不同了:
//Tasks
[
{
_id: ObjectID('AAAA'),
task_number: 1234,
taks_name: 'Prepare MongoDB environment',
due_date: '2017-01-15'
},
{
_id: ObjectID('BBBB'),
task_number: 1235,
taks_name: 'Import Test Data',
due_date: '2017-02-15'
},
]
//Persons
{
name: 'Kate Monster',
role: 'Manager',
tasks : [
ObjectID('AAAA'),
ObjectID('BBBB')
]
}
有得必有失,当我们需要查询Person承担的Tasks时,采用这种方式,就需要采用application-level join方式执行两次查询。
这种建模方式还带来另一种可能,就是原本Person->Tasks的one-to-N关系就可以变为N-to-N关系,因为一个Task可以被多个Person所拥有。如果采用Embedded方式,则会导致Task数据的冗余。
在文章 6 Rules of Thumb for MongoDB Schema Design中,作者将这种1对N关联实现的判断依据划分为三种形式:
- one-to-few
- one-to-many
- one-to-squillions
但我认为该怎么实现关联,应该从Entity之间的领域关系来判断,我们可以引入DDD的Aggregation设计概念作为建模的依据。简单来说,如果使用Embedded,可以认为该Entity处于Aggregation边界之内,对外应该通过Aggregation Root来访问。文章 6 Rules of Thumb for MongoDB Schema Design的说法就是:
Will the entities on the “N” side of the One-to-N ever need to stand alone?
如果是Stand Alone,就意味着该Entity可以成为一个独立的Aggregation,然后再通过ID与另外一个Aggregate关联。
在SegmentFault上则有人做了如此总结:
- FirstClass (比如“User”这种) 应该用独立的Collection
- "条目类型"的,应该 embedded
- 两个模型之间如果是包含关系,用 embedded
- 多对多关系,用 link(类似sql里面的foregin key)
- 如果一个模型,其可能存的对象很少,那么就用独立的collection,这样有助于mongodb server做缓存
- embedded方式不利于做复杂的关联,复杂的查询
- embedded方式性能很有优势,如果你有“性能”方面的要求,可以考虑用embbed
- linux运维中的命令梳理(三)
- 轻松水印-批量提取exif信息加水印的工具
- Enterprise Library 4.1学习笔记7----缓存应用程序块之SqlDependency
- linux运维中的命令梳理(一)
- 可视化你的BLAST结果
- linux运维中的命令梳理(二)
- VB-取日期属于星期几
- 全球AI新闻创新实践系列③:华邮、雅虎、美联社、Quartz怎么干!
- nginx+php负载均衡集群环境中的session共享方案梳理
- PowerPoint发布及链接图片的处理
- EXCEL单元格的引用方式
- SqlDependency学习笔记
- linux系统下对网站实施负载均衡+高可用集群需要考虑的几点
- 运维利器-ClusterShell集群管理操作记录
- JavaScript 教程
- JavaScript 编辑工具
- JavaScript 与HTML
- JavaScript 与Java
- JavaScript 数据结构
- JavaScript 基本数据类型
- JavaScript 特殊数据类型
- JavaScript 运算符
- JavaScript typeof 运算符
- JavaScript 表达式
- JavaScript 类型转换
- JavaScript 基本语法
- JavaScript 注释
- Javascript 基本处理流程
- Javascript 选择结构
- Javascript if 语句
- Javascript if 语句的嵌套
- Javascript switch 语句
- Javascript 循环结构
- Javascript 循环结构实例
- Javascript 跳转语句
- Javascript 控制语句总结
- Javascript 函数介绍
- Javascript 函数的定义
- Javascript 函数调用
- Javascript 几种特殊的函数
- JavaScript 内置函数简介
- Javascript eval() 函数
- Javascript isFinite() 函数
- Javascript isNaN() 函数
- parseInt() 与 parseFloat()
- escape() 与 unescape()
- Javascript 字符串介绍
- Javascript length属性
- javascript 字符串函数
- Javascript 日期对象简介
- Javascript 日期对象用途
- Date 对象属性和方法
- Javascript 数组是什么
- Javascript 创建数组
- Javascript 数组赋值与取值
- Javascript 数组属性和方法
- 【C++】 使用sort函数进行容器排序
- 宝塔如何安装 MDClub
- Jackson 的 JsonManagedReference 和 JsonBackReference 注解
- leetcode哈希表之独一无二的出现次数
- Vue 网站首页加载优化
- Swift日常开发随笔
- vue入门003~vue项目引入element并创建一个登录页面
- vue入门002~vue项目的两种创建方式
- IntelliJ IDEA,WebStorm,PhpStorm破解到2089年
- 小程序订阅消息推送(含源码)java实现小程序推送,springboot实现微信消息推送
- 借助云开发10行代码实现短信验证码的发送
- 借助云开发实现小程序订阅消息(模板消息)推送功能
- 1小时实战入门小程序开发,历史上的今天案例讲解
- 小程序实现全屏幕高斯模糊背景图
- 小程序顶部导航栏,可滑动,可动态选中放大