MongoDB
文档导向的 NoSQL 数据库
非关系型数据库中功能最丰富,最像关系型数据库
一、选用前提条件
○ 满足其中 2 个条件,选 Mongo 就很好
○ 满足其中 3 个条件,直接选 Mongo
-
不需要 事务、复杂 join - 强事务场景 选择 关系型数据库
-
数据模型无法确定
-
需要 2000 以上的 读写 QPS
-
需要 TB、PB 级别的数据存储
-
需要 能快速水平扩展
○ 水平扩展 : 增加服务器数量,扩充系统性能
○ 垂直扩展 : 提升单机处理能力
-
需要 99.999% 高可用
-
大量 地理位置、文本查找
二、适用场景
○ 适合 关系型数据库中,【一对多】的场景
○ 适合 大尺寸、低价值 的 BSON 数据存储
- 游戏场景 - 玩家装备、积分
- 物流场景 - 物流订单状态
- 社交场景 - 附近的人、地点
- 物联网场景 - 智能设备日志信息
- 直播场景 - 主播礼物
三、名词解释
-
NoSQL : Not Only SQL - 不要求结构化存储
SQL 为结构化查找语言
-
BSON : Binary JSON
- 类 JSON 的二进制存储格式
- 有一些 JSON 没有的数据类型
- 优点 - 灵活性高
- 缺点 - 空间利用率不理想 - 存在重复键名之类的冗余信息
○ JSON 是使用 字符串 方式存储数据
○ BSON 是使用 二进制 方式存储数据
四、概念对比
RDBMS | MongoDB |
---|---|
database 数据库 | database 数据库 |
table 表 | collection 集合 |
row 行 | document BSON 文档 |
column 列 | field 字段 |
index 索引 | index 索引 |
join 主外键关联 | embedded Document 嵌套文档 |
primary key 指定 1 至 N 个列做主键 | primary key 指定 _id field 做为主键 |
五、BSON 类型
【插入数据】
○ db.集合名.insert(文档) - 插入数据主键已存在,则报异常
○ db.集合名.insertOne(文档) - 插入数据主键存在则更新,不存在则插入
-
String : 字符串 - { key : "cba" }
-
Integer : 整数 - { key : 1 }
-
Boolean : 布尔值 - { key : true }
-
Double : 双精度符点 - { key : 3.14 }
-
ObjectId : 对象 ID - { _id : new ObjectId() }
由 时间戳、机器码、进程ID、随机数 组成
-
Array : 数组 - { arr : [ "a", "b"] }
-
Timestamp : 时间戳 - { ts : new Timestamp() }
-
Object : 内嵌文档 - { o : { foo : "bar" } }
-
Null : 空值 - { key : null }
-
Date、ISODate : 格林威治时间 - { birth : new Date() }
-
Code : 代码 - { x : function(){} }
-
File : 文档 - fs.files、fs.chunks
六、条件查找
db.集合名.find({条件}).sort({排序字段:排序方式})).skip(跳过的行数).limit(一页显示多少数据)
操作 | 条件格式 | 范例 | RDBMS 中的语意 |
---|---|---|---|
等于 | { key : value } | db.集合名.find( { 字段名 : 值 } ).pretty() | where 字段名 = 值 |
大于 | { key : { $gt : value } } | db.集合名.find( { 字段名 : { $gt : 值 } } ).pretty() | where 字段名 > 值 |
小于 | { key : { $lt : value } } | db.集合名.find( { 字段名 : { $lt : 值 } } ).pretty() | where 字段名 < 值 |
大于等于 | { key : { $gte : value } } | db.集合名.find( { 字段名 : { $gte : 值 } } ).pretty() | where 字段名 >= 值 |
小于等于 | { key : { $lte : value } } | db.集合名.find( { 字段名 : { $lte : 值 } } ).pretty() | where 字段名 <= 值 |
不等于 | { key : { $ne : value } } | db.集合名.find( { 字段名 : { $ne : 值 } } ).pretty() | where 字段名 != 值 |
and | { key1 : value1, key2 : value2 } | db.集合名.find( { key1 : value1, key2 : value2 } ).pretty() | where 字段名1 = 值1 and 字段名2 = 值2 |
or | { $or : [ { key1 : value1 }, { key2 : value2 } ] | db.集合名.find( { $or : [ { key1 : value1 }, { key2 : value2 } ] } ).pretty() | where 字段名1 = 值1 or 字段名2 = 值2 |
not | { key : { not : { 操作符 : value } } | db.集合名.find( { key : { not : { 操作符 : value } } ).pretty() | where not 字段名 $操作符 值 |
七、数据更新
db.集合名.update(
<查找条件>,
<更新方式>,
{
upsert: <boolean>,
multi: <boolean>,
writeConcern: <document>
}
)
复制代码
1. 更新方式
- $set : 设置字段值
- $unset : 删除字段
- $inc : 对修改的字段值进行增减 - 可以为 正值 或 负值
2. 属性
- upsert : 如果不存在要 update 的纪录,是否进行插入 - 【默认】false
- multi : 只更新找到的第一条纪录 - 【默认】false
- writeConcern : 可靠性属性
- w
- 【默认】1 : 所有副本确认
- 0 : 不要求副本确认
- majority : 多数副本确认
- j
- true : 操作写入硬盘才能返回 ACK
- false : 操作不必写入硬盘就能返回 ACK
- wtimeout : 操作时间限制
- w
db.col.update( { name : "张三" }, { $inc : { expectSalary : 3000 } }, { upsert : ture } )
复制代码
八、数据删除
db.集合名.remove(
<查找条件>,
{
justOne: <boolean>,
writeConcern: <document>
}
)
复制代码
1. 属性
- justOne : 是否只删除一个文档 - 【默认】false
- writeConcern : 可靠性属性
db.col.remove( { name : "张三" } )
复制代码
九、聚合操作
1. 单目的聚合
- count() : 统计数量
- distinct() : 去重并返回属性值
db.集合名.find({}).count()
复制代码
2. 聚合管道
1. 操作
- $group : 文档分组
- 用于统计不同类型数据
- $project : 修改输出文档结构
- 重命名
- 增加、删除 域
- 创建计算结果
- 嵌套文档
- $match : 过滤数据,只输出符合条件的文档
- $limit : 限制聚合管道返回的文档数
- $skip : 跳过指定文档数量,返回剩余的文档
- $sort : 将文档排序后输出
- $geoNear : 输出接近某一地理位置的有序文档
2. 表达式
- $sum : 计算总合
- $avg : 计算平均值
- $min : 获取集合中所有文档的最小值
- $max : 获取集合中所有文档的最大值
- $push : 将结果文档的值,插入到一个数组中
- $addToSet : 将结果文档的值,插入到一个数组中,但数据不重复
- $first : 获取第一个文档数据
- $last : 获取最后一个文档数据
db.col.aggregate([
{$group : {_id: "$city"/*按照 city 字段分组*/, city_count /*聚合结果字段名称*/ : { $sum /*聚合方式*/ : 1 } } }
])
db.col.aggregate([
{$group : {_id: "$city", avgSal:{$avg:"$expectSalary"}}},
{$project : {city: "$city", salary : "$avgSal" }} /*将 avgSal 字段改为 salary*/
])
db.col.aggregate([
{$group:{_id: "$city",count:{$sum : 1}}},
{$match:{count:{$gt:1}}} /*统计数量大于 1 才进行显示*/
])
复制代码
3. MapReduce
○ 在多台 Server 上 并行 运行 聚合逻辑
○ 如果一个聚合操作消耗 20% 以上的内存,将被迫中止,并输出错误消息
db.col.mapReduce(
function() {emit(key,value);}, // Map 方法
function(key,values) {return reduceFunction}, // Reduce 方法
{
out: collection,
query: document,
sort: document,
limit: number,
finalize: <function>,
verbose: <boolean>
}
)
db.col.mapReduce(
function() { emit(this.city,this.expectSalary); },
function(key, value) {return Array.avg(value)},
{
query:{expectSalary:{$gt: 15000}},
out:"cityAvgSal"
}
)
复制代码
-
Map 方法 : Javasript 方法,负责将一个 输入文档 转换成零或多个 输出文档,作为 Reduce 方法的参数
-
Reduce 方法 : Javasript 方法,对 Map 方法的输出进行合并计算 - 相同 key 会前往同一个 Reduce 方法
-
out : 统计结果 存放的集合
-
query : 筛选条件,满足条件的文档才会调用 Map 函数
-
sort : 对发往 Map 函数前的文档进行排序
-
limit : 限制发往 Map 函数的数量
sort、limit 必须结合使用才有意义
-
finalize : 对 Reduce 输出结果进行修改
-
verbose : 是否包含结果信息中的时间信息 - 【默认】false
十、索引类型
○【目的】提高查找效率
○【默认】对 _id 创建唯一索引
○【底层实现】B树
○【排序方式】1 : 升序,-1 : 降序
- 单键索引
- 对单个字段的索引
- db.集合名.createIndex( { "字段名" : 排序方式 } )
- 过期索引
- 文档在一定时间后自动删除
- 字段必须为日期类型
- db.集合名.createIndex( { "日期字段" : 排序方式 }, { expireAfterSeconds : 秒数 } )
- 复合索引
- 在多个字段上创建索引
- 需注意 索引顺序、排序方式
- db.集合名.createIndex( { "字段名1" : 排序方式, "字段名2" : 排序方式 } )
- 多键索引
- 针对数组中的每一个元素创建索引
- 在复合索引中只能有一个多键索引
- 创建方式与创建普通索引方式相同
- db.集合名.createIndex( { "字段名" : 排序方式 } )
- 地理空间索引
- 针对地理空间座标创建索引
- 2dsphere : 球面上的点
- 2d : 平面上的点
- db.集合名.ensureIndex( { "字段名" : "2dsphere" } )
- 全文索引
- 一个集合最多一个全文索引
- 中文分词器不理想 - 推荐改用 ES
- db.集合名.createIndex( { "字段名" : "text" } )
- 哈希索引
- 仅支持等值查找,不支持范围查找
- db.集合名.createIndex( { "字段" : "hashed" } )
十一、数据模型
-
内嵌
-
文档嵌套文档
-
适用场景
- 文档间为 一对一、一对多 关系
- 经常一起读取的数据
- 有 Map-Reduce 聚合需求的数据 - Map-Reduce 只能操作单个集合
{ "_id":"ObjectId("xxxxx")", "name":"Zhang san", "classes":[ { "class":"Math", "credits":"5", "room":"204" }, { "class":"English", "credits":"5", "room":"305" } ] } 复制代码
-
-
引用
-
文档存储另一个文档的引用信息
-
当 内嵌 会导致过多数据重复
-
文档间为 多对多 关系
{ "_id":"ObjectId("xxxxx")", "name":"Zhang san", "classes":[ { "_id":"ObjectId("xxx")", "class":"Math" }, { "_id":"ObjectId("xxx")", "class":"English" } ] } 复制代码
-
十二、高可用
1. 角色
-
Primary 主节点
-
负责 读写 操作
-
纪录 增、删、改 操作到 oplog 中 - oplog 具备幂等性
幂等性 : 多次运行的结果与只运行一次的结果相同
-
-
Secondary 从节点
-
负责 读 操作
-
透过复制 oplog,使数据与 Primary 保持一致
-
-
ArbiterOnly 仲裁节点
- 负责 投票选出 Primary
- 不能 增、删、改 数据
- 不能 变成 Primary
2. 同步类型
- 初始化同步
- 全量从 Primary 同步数据
- 触发情形
- Secondary 第一次加入
- Secondary 落后的数据量超过 oplog 的大小
- Keep 复制同步
- 增量从 Primary 同步数据
3. 心跳检测
- 每 2 秒向其他节点发送一次 Ping 包,10 秒内未响应则将该节点标记为不能访问
- 每个节点都会维护一份 状态映射表,纪录其他节点的 角色、日志时间戳 ... 等信息
- Primary 发现自己无法与大部分节点通信,则会把自己降为 Secondary
4. 主节点击举
- 触发时机
- Secondary 发现权重比 Primary 高,发起 替换选举
- Secondary 发现集群中没有 Primary 时,发起 选举
- Primary 发现不能访问到其他大部分节点时,主动降级
- 选举过程
- 检测自身是否符合候选人资格,若符合则成为 发起者,并进行 FreshnessCheck
- 发起者 向 存活节点 发送 Elect 请求
- 仲裁者 收到请求会运行合法性检查,若检查通过,则 仲裁者 给 发起者 投一票
- 发起者 若获得投票超过半数,则成为 Primary
5. 分片
1.角色
- Shard Server
- 负责存储数据
- 由 一个 或 多个 mongod 进程组成,每个进程保存相同的数据分片
- 保存不同数据分片的 Shard Server,可组合成 Shards Server 集群
- Router Server
- 集群入口,负责将请求转发到对应的 Shard Server 上
- Config Server
- 负责存储数据库 路由、分片 配置
2. 概念
- Shard Key 分片主键
- 用于确定数据该存放在哪个 Chunk
- Chunk 区块
- Shard Server 内的一部分数据
- 一个 Shard Server 由多个 Chunk 组成
- 基于 左闭右开 的区间范围
3. 分片策略
-
范围分片
- 基于 Shard Key 的值切分数据,每个 Chunk 将分配到一个范围
- 适合范围查找
- 缺点 : 当 Shard Key 属于 递增 或 递减 趋势(比如,自动生成ID - 时间戳),则添加的数据都会被分到同一个 Chuck,则该 Chunk 写压力大
-
哈希分片
-
将文档随机分散到各个 Chunk,扩展写能力
-
缺点 : 不能高效范围查找
-
近期评论