聚合操作
聚合管道
db.COLLECTION_NAME.aggregate(AGGREGATE_OPERATION)
MongoDB中聚合(aggregate)主要⽤于统计数据(诸如统计平均值,求和等),并返回计算后的数据结果。
表达式 描述
$sum 计算总和
$avg 计算平均值
$min 获取集合中所有⽂档对应值得最⼩值
$max 获取集合中所有⽂档对应值得最⼤值
$push 在结果⽂档中插⼊值到⼀个数组中
$addToSet 在结果⽂档中插⼊值到⼀个数组中,但数据不重复
$first 根据资源⽂档的排序获取第⼀个⽂档数据
$last 根据资源⽂档的排序获取最后⼀个⽂档数据
MongoDB 中使⽤ db.COLLECTION_NAME.aggregate([{},…]) ⽅法来构建和使⽤聚合管道,每个⽂档通过⼀
个由⼀个或者多个阶段(stage)组成的管道,经过⼀系列的处理,输出相应的结果。
MongoDB的聚合管道将MongoDB⽂档在⼀个管道处理完毕后将结果传递给下⼀个管道处理。管道操作是可以重复
的。
这⾥我们介绍⼀下聚合框架中常⽤的⼏个操作:
- $group:将集合中的⽂档分组,可⽤于统计结果。
- $project:修改输⼊⽂档的结构。可以⽤来重命名、增加或删除域,也可以⽤于创建计算结果以及嵌套⽂档。
- m a t c h : ⽤ 于 过 滤 数 据 , 只 输 出 符 合 条 件 的 ⽂ 档 。 match:⽤于过滤数据,只输出符合条件的⽂档。 match:⽤于过滤数据,只输出符合条件的⽂档。match使⽤MongoDB的标准查询操作。
- $limit:⽤来限制MongoDB聚合管道返回的⽂档数。
- $skip:在聚合管道中跳过指定数量的⽂档,并返回余下的⽂档。
- $sort:将输⼊⽂档排序后输出。
- $geoNear:输出接近某⼀地理位置的有序⽂档。
db.lg_resume_preview.aggregate(
[{$group : {_id: "$city", avgSal:{$avg:"$expectSalary"}}},
{$project : {city: "$city", salary : "$avgSal"}}
])
db.lg_resume_preview.aggregate(
[{$group:{_id: "$city",count:{$sum : 1}}},
{$match:{count:{$gt:1}}}
])
MapReduce 编程模型
介绍:Pipeline查询速度快于MapReduce,但是MapReduce的强⼤之处在于能够在多台Server上并⾏执⾏复杂的聚合逻辑。MongoDB不允许Pipeline的单个聚合操作占⽤过多的系统内存,如果⼀个聚合操作消耗20%以上的内存,那
么MongoDB直接停⽌操作,并向客户端输出错误消息。
MapReduce是⼀种计算模型,简单的说就是将⼤批量的⼯作(数据)分解(MAP)执⾏,然后再将结果合并成最终结果(REDUCE)
>db.collection.mapReduce(
function() {emit(key,value);}, //map 函数
function(key,values) {return reduceFunction}, //reduce 函数
{
out: collection,
query: document,
sort: document,
limit: number,
finalize: <function>,
verbose: <boolean>
}
)
使⽤ MapReduce 要实现两个函数 Map 函数和 Reduce 函数,Map 函数调⽤ emit(key, value), 遍历 collection 中所有的记录, 将 key 与 value 传递给 Reduce 函数进⾏处理。
- map:是JavaScript 函数,负责将每⼀个输⼊⽂档转换为零或多个⽂档,⽣成键值对序列,作为 reduce 函数参数
- reduce:是JavaScript 函数,对map操作的输出做合并的化简的操作(将key-value变成key-values,也就是把values数组变成⼀个单⼀的值value)
- out:统计结果存放集合
- query: ⼀个筛选条件,只有满⾜条件的⽂档才会调⽤map函数。
- sort: 和limit结合的sort排序参数(也是在发往map函数前给⽂档排序),可以优化分组机制
- limit: 发往map函数的⽂档数量的上限(要是没有limit,单独使⽤sort的⽤处不⼤)
- finalize:可以对reduce输出结果再⼀次修改
- verbose:是否包括结果信息中的时间信息,默认为fasle
db.lg_resume_preview.mapReduce(
function() { emit(this.city,this.expectSalary); },
function(key, value) {return Array.avg(value)},
{
query:{expectSalary:{$gt: 15000}},
out:"cityAvgSal"
}
)
MongoDB索引Index
介绍:索引⽬标是提⾼数据库的查询效率,没有索引的话,查询会进⾏全表扫描(scan every document in a collection),数据量⼤时严重降低了查询效率。默认情况下Mongo在⼀个集合(collection)创建时,⾃动地对集合的_id创建了唯⼀索引。
单键索引 (Single Field)
对于单个字段索引,索引键的排序顺序⽆关紧要,因为MongoDB可以在任⼀⽅向读取索引。
对于单个字段索引,索引键的排序顺序⽆关紧要,因为MongoDB可以在任⼀⽅向读取索引。 1 正序 0 倒叙
create:
db.集合名.createIndex({"字段名":排序⽅式})
- 特殊的单键索引 过期索引 TTL ( Time To Live )
TTL索引是MongoDB中⼀种特殊的索引, 可以⽀持⽂档在⼀定时间之后⾃动过期删除,⽬前TTL索引只能在单字段上建⽴,并且字段类型必须是⽇期类型。
db.集合名.createIndex({"⽇期字段":排序⽅式}, {expireAfterSeconds: 秒数})
复合索引(Compound Index)
通常我们需要在多个字段的基础上搜索表/集合,这是⾮常频繁的。 如果是这种情况,我们可能会考虑在MongoDB中制作复合索引。 复合索引⽀持基于多个字段的索引,这扩展了索引的概念并将它们扩展到索引中的更⼤域。 制作复合索引时要注意的重要事项包括:字段顺序与索引⽅向。
db.集合名.createIndex( { "字段名1" : 排序⽅式, "字段名2" : 排序⽅式 } )
多键索引(Multikey indexes)
- 针对属性包含数组数据的情况,MongoDB⽀持针对数组中每⼀个element创建索引,Multikey indexes⽀持strings,numbers和nested documents
地理空间索引(Geospatial Index)
针对地理空间坐标数据创建索引。
- 2dsphere索引,⽤于存储和查找球⾯上的点
- 2d索引,⽤于存储和查找平⾯上的点
db.company.insert(
{
loc : { type: "Point", coordinates: [ 116.482451, 39.914176 ] },
name: "⼤望路地铁",
category : "Parks"
}
)
db.company.ensureIndex( { loc : "2dsphere" } )
参数不是1或-1,为2dsphere 或者 2d。还可以建⽴组合索引。
db.company.find({
"loc" : {
"$geoWithin" : {
"$center":[[116.482451,39.914176],0.05]
}
}
})
全⽂索引
MongoDB提供了针对string内容的⽂本查询,Text Index⽀持任意属性值为string或string数组元素的索引查询。
- 注意:⼀个集合仅⽀持最多⼀个Text Index,中⽂分词不理想 推荐ES
db.集合.createIndex({"字段": "text"})
db.集合.find({"$text": {"$search": "coffee"}})
哈希索引 Hashed Index
针对属性的哈希值进⾏索引查询,当要使⽤Hashed index时,MongoDB能够⾃动的计算hash值,⽆需程序计算hash值。注:hash index仅⽀持等于查询,不⽀持范围查询。
db.集合.createIndex({"字段": "hashed"})
索引和explain 分析
索引管理
创建索引并在后台运⾏
db.COLLECTION_NAME.createIndex({"字段":排序⽅式}, {background: true});
获取针对某个集合的索引
db.COLLECTION_NAME.getIndexes()
索引的⼤⼩
db.COLLECTION_NAME.totalIndexSize()
索引的重建
db.COLLECTION_NAME.reIndex()
索引的删除
db.COLLECTION_NAME.dropIndex("INDEX-NAME")
db.COLLECTION_NAME.dropIndexes()
注意: _id 对应的索引是删除不了的
explain 分析
explain()也接收不同的参数,通过设置不同参数我们可以查看更详细的查询计划。
- queryPlanner:queryPlanner是默认参数,具体执⾏计划信息参考下⾯的表格。
- executionStats:executionStats会返回执⾏计划的⼀些统计信息(有些版本中和allPlansExecution等同)。
- allPlansExecution:allPlansExecution⽤来获取所有执⾏计划,结果参数基本与上⽂相同。
MongoDB 索引底层实现原理
- MongoDB 是⽂档型的数据库,它使⽤BSON 格式保存数据,⽐关系型数据库存储更⽅便。
- 我们可以把⼀条数据和这条数据对应的数据都存⼊⼀个BSON对象中,这种形式更简单,通俗易懂。
- MongoDB使⽤B-树,所有节点都有Data域,只要找到指定索引就可以进⾏访问,单次查询从结构上来看要快于MySql。
B-树的特点:
(1) 多路 ⾮⼆叉树
(2) 每个节点 既保存数据 ⼜保存索引
(3) 搜索时 相当于⼆分查找
B+树
B+ 树的特点:
(1) 多路⾮⼆叉
(2) 只有叶⼦节点保存数据
(3) 搜索时 也相当于⼆分查找
(4) 增加了 相邻节点指针
从上⾯我们可以看出最核⼼的区别主要有俩,⼀个是数据的保存位置,⼀个是相邻节点的指向。就是这俩造成了
MongoDB和MySql的差别。
- B+树相邻接点的指针可以⼤⼤增加区间访问性,可使⽤在范围查询等,⽽B-树每个节点 key 和 data 在⼀起适合随机读写 ,⽽区间查找效率很差。
- B+树更适合外部存储,也就是磁盘存储,使⽤B-结构的话,每次磁盘预读中的很多数据是⽤不上的数据。因此,它没能利⽤好磁盘预读的提供的数据。由于节点内⽆ data 域,每个节点能索引的范围更⼤更精确。
- 注意这个区别相当重要,是基于(1)(2)的,B-树每个节点即保存数据⼜保存索引 树的深度⼤,所以磁盘IO的次数多,B+树只有叶⼦节点保存,较B树⽽⾔深度⼩,磁盘IO少,有利于区间访问。
MongoDB的适⽤场景
● ⽹站数据:Mongo ⾮常适合实时的插⼊,更新与查询,并具备⽹站实时数据存储所需的复制及⾼度伸缩性。
● 缓存:由于性能很⾼,Mongo 也适合作为信息基础设施的缓存层。在系统重启之后,由Mongo 搭建的持久化缓存层可以避免下层的数据源过载。
● ⼤尺⼨、低价值的数据:使⽤传统的关系型数据库存储⼀些⼤尺⼨低价值数据时会⽐较浪费,在此之前,很多时候程序员往往会选择传统的⽂件进⾏存储。
● ⾼伸缩性的场景:Mongo ⾮常适合由数⼗或数百台服务器组成的数据库,Mongo 的路线图中已经包含对MapReduce 引擎的内置⽀持以及集群⾼可⽤的解决⽅案。
● ⽤于对象及JSON 数据的存储:Mongo 的BSON 数据格式⾮常适合⽂档化格式的存储及查询。
⾏业具体应⽤场景
- 游戏场景,使⽤ MongoDB 存储游戏⽤户信息,⽤户的装备、积分等直接以内嵌⽂档的形式存储,⽅便查询、更新。
- 物流场景,使⽤ MongoDB 存储订单信息,订单状态在运送过程中会不断更新,以 MongoDB 内嵌数组的形式来存储,⼀次查询就能将订单所有的变更读取出来。
- 社交场景,使⽤ MongoDB 存储存储⽤户信息,以及⽤户发表的朋友圈信息,通过地理位置索引实现附近的⼈、地点等功能。
- 物联⽹场景,使⽤ MongoDB 存储所有接⼊的智能设备信息,以及设备汇报的⽇志信息,并对这些信息进⾏多维度的分析。
- 直播,使⽤ MongoDB 存储⽤户信息、礼物信息等。
MongoDB的数据模型
内嵌: 内嵌的⽅式指的是把相关联的数据保存在同⼀个⽂档结构之中。MongoDB的⽂档结构允许⼀个字段或者⼀个数组内的值作为⼀个嵌套的⽂档。
例如: 用户的个人信息中嵌入订单相关的集合。等等,要避免过多的重复数据嵌入,造成空间的浪费和查询速度的降低。
引⽤: 引⽤⽅式通过存储数据引⽤信息来实现两个不同⽂档之间的关联,应⽤程序可以通过解析这些数据引⽤来访问相关数据。
如何选择数据模型
选择内嵌:
- 数据对象之间有包含关系 ,⼀般是数据对象之间有⼀对多或者⼀对⼀的关系 。
- 需要经常⼀起读取的数据。
- 有 map-reduce/aggregation 需求的数据放在⼀起,这些操作都只能操作单个 collection。
选择引⽤:
- 当内嵌数据会导致很多数据的重复,并且读性能的优势⼜不⾜于覆盖数据重复的弊端 。
- 需要表达⽐较复杂的多对多关系的时候 。
- ⼤型层次结果数据集 嵌套不要太深。
MongoDB 存储引擎
- 存储引擎是MongoDB的核⼼组件,负责管理数据如何存储在硬盘和内存上。MongoDB⽀持的存储引擎有MMAPv1,WiredTiger和InMemory。
- InMemory存储引擎⽤于将数据只存储在内存中,只将少量的元数据(meta-data)和诊断⽇志(Diagnostic)存储到硬盘⽂件中,由于不需要Disk的IO操作,就能获取所需的数据,InMemory存储引擎⼤幅度降低了数据查询的延迟(Latency)。
- 从mongodb3.2开始默认的存储引擎是WiredTiger,3.2版本之前的默认存储引擎是MMAPv1,mongodb4.x版本不再⽀持MMAPv1存储引擎。
WiredTiger存储引擎比MMAPv1的优势
1.⽂档空间分配⽅式
WiredTiger使⽤的是BTree存储 MMAPV1 线性存储 需要Padding
2.并发级别
WiredTiger ⽂档级别锁 MMAPV1引擎使⽤表级锁
3.数据压缩
snappy (默认) 和 zlib ,相⽐MMAPV1(⽆压缩) 空间节省数倍。
4.内存使⽤
WiredTiger 可以指定内存的使⽤⼤⼩。
5.Cache使⽤
WT引擎使⽤了⼆阶缓存WiredTiger Cache, File System Cache来保证Disk上的数据的最终⼀致性。
⽽MMAPv1 只有journal ⽇志。
WiredTiger存储引擎实现原理
- 写请求 (产生快照文件之前先进行日志的记录,不到60秒的数据从Journal中恢复)
WiredTiger的写操作会默认写⼊ Cache ,并先一步持久化到 WAL (Write Ahead Log)日志文件当中,每60s或Log⽂件达到2G做⼀次 checkpoint 强制 journal ⽂件的同步 ,(当然我们也可以通过在写⼊时传⼊ j: true 的参数writeConcern { w: , j: , wtimeout: } 产⽣快照⽂件)。WiredTiger初始化时,恢复⾄最新的快照状态,然后再根据WAL恢复数据,保证数据的完整性。
- 缓存到刷盘
Cache是基于BTree的,节点是⼀个page,
root page是根节点,internal page是中间索引节点,leaf page真正存储数据,数据以page为单位读写。WiredTiger采⽤Copy on write(复制出一份进行修改,将修改后的数据刷到磁盘)的⽅式管理写操作(insert、update、delete),写操作会先缓存在cache⾥,持久化时,写操作不会在原来的leaf page上进⾏,⽽是写⼊新分配的page,每次checkpoint都会产⽣⼀个新的root page。
- checkPoint (刷盘)流程
- 对所有的table进⾏⼀次checkpoint,每个table的checkpoint的元数据更新⾄WiredTiger.wt
- 对WiredTiger.wt进⾏checkpoint,将该table checkpoint的元数据更新⾄临时⽂件
WiredTiger.turtle.set - 将WiredTiger.turtle.set重命名为WiredTiger.turtle。
- 上述过程如果中间失败,WiredTiger在下次连接初始化时,⾸先将数据恢复⾄最新的快照状态,然后根据WAL恢复数据,以保证存储可靠性。
- WiredTiger.basecfg: 存储基本配置信息,与 ConfigServer有关系
- WiredTiger.lock: 定义锁操作
- table*.wt: 存储各张表的数据
- WiredTiger.wt: 存储table* 的元数据
- WiredTiger.turtle: 存储WiredTiger.wt的元数据
- journal: 存储WAL(Write Ahead Log)
MongoDB 事务
- MongoDB支持文档级别的ACID事务;今天,MongoDB不支持多文档事务。对于许多(但不是全部)应用程序,这就足够了,因为记录的数据往往作为单个文档进行管理。与大多数数据库一样,MongoDB使用磁盘日志的预写日志记录来保证写入操作的持久性并提供崩溃弹性。