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
- 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 数组属性和方法
- 为何IntelliJ IDEA比Eclipse好在哪里?
- 五分钟C语言数据结构 之 二叉树中序遍历
- Django 安全之跨站点请求伪造(CSRF)保护
- 五分钟C语言数据结构 之 二叉树先序遍历
- Java 语言基础(常用设计原则和设计模式,常用 Java 8~11 新特性)
- 五分钟C语言数据结构 之 二叉树后序遍历(非递归很重要)
- 5分钟Flink - 自定义Source源
- 9.深入k8s:调度器及其源码分析
- 5分钟Flink - 自定义Data Sink
- 5分钟Flink - 流处理API转换算子集合
- 视频上云/网络穿透/网络映射服务EasyNTS前端组织添加页面出现Vue冲突怎么解决?
- Pinpoint 一款强大的APM工具
- 1. Pandas系列 - 基本数据结构
- 6 年前,只会 JSP 和 Servlet 就可以找到工作
- Python文件处理实用指南