阅读 670

MongoDB 基础教程

MongoDB

特点

  1. 不采用关系模型是为了获得更好的扩展性。
    1. 默认支持横向扩展
    2. 文档存储结构易于在多台服务器之间进行数据分割。
  2. 采用文档结构存储数据可以来表现更加复杂的层次关系。
  3. 不适用预定义模式,文档在使用过程中需要定义建和值得类型。
  4. 功能丰富,支持各种索引和文件存储。
  5. 性能卓越,代价就是需要很多内存。

基础知识

  • 文档是MongoDB中数据基本单元
  • 集合是一种具有动态模式得表,即该结构不限制键和值得类型。
  • 每个文档默认都有一个特殊得键_id,该键在文档所属得集合中是唯一。
  • MongoDB使用Javascript shell来管理MongoDB

文档

文档的键是字符,定义规则如下

  • 键中不能包含\0空字符,该字符用于表示键结尾。
  • .$具有特殊意义,故尽量避免在键中使用这两个保留字符。
  • 键区分大小写,还区分类型。
  • 键在文档中不重复。
  • 文档中的键/值对是有序的,{"x": 1, "y": 2}与{"y": 2, "x": 1}是不同的文档。

集合

集合就是一组文档。文档相当于关系数据库中表的一行,集合相当于表。

  • 集合是动态模式。即集合中的文档数据结构各种各样的。但不推荐在一个集合中存放各种各样的结构的数据。

    • 查询困难,慢
    • 创建索引困难
  • 集合名不能是空字符串、\0字符、不能是system.开头、不能包含$

  • GridFS专门用于存储大文件的协议,结合自己和来存储文件元数据,这样就可以将文件和内容块很好的隔离开来。

数据库

多个集合构成数据库,数据命名规范

  • 不能是空字符串
  • 不得含有/、\、.、"、*、<、>、:、|、?、$、\0。
  • 区分大小写
  • 数据库名最多64字节

保留数据库如下

  • admin存放身份验证得地方。
  • local 该数据库不可复制,且一台服务器上得所有本地鸡和都可以存储在这个数据库中。
  • config 用于分片设置时,分片信息会存储在config数据库中。

命名空间:数据库.集合名。可以组合成完全限定名。

数据类型

MongoDB在保留了JSON基本键/值对特性基础上,添加了一些其他数据类型。

基本数据类型

  • null 表示空值或者不存在得字段
  • 布尔类型 true 和 false
  • 数值,默认为64位浮点数值,整型值位NumberInt("3")、NumberLong("4")
  • 字符串,UTF-8字符集
  • 日期,被存储位自新纪元依赖经过得毫秒数,不存储时区,故项目在多地保存,需要增加键来存储时区。new Date()
  • 正则表达式,如{“x”: /foobar/i}
  • 数组
  • 内嵌文档
  • 对象Id,如{"x": ObjectId()}
  • 二进制数据
  • 代码,查询和文档中可以包含任意JavaScript代码

_id和ObjectId

MongodB中存储得文档必须有一个"_id"键,这个键可以是任意类型,默认是个ObjectId对象。在一个集合里面,每个文档都有唯一的__id

ObjectId是集合中_id的默认类型。ObjectId使用12字节存储空间,是一个由24个十六进制数字组成的字符串。

ObjectId生成方式

image-20210225160336385

文档在创建的时候由MongoDB自动生成ObjectId,通常会在客户端由驱动程序生成。这一做法体现了能交给客户端驱动程序来做的事情就不要交给服务器来做。

MongoDB shell

mongo shell连接服务器方法

$ mongo [options] host:port/databaseName scriptFilename1 ... scriptFilenameN 
复制代码
  • --nodb参数启动shell,不连接任何数据库
  • scriptFilename1 ... scriptFilenameN 脚本文件名称

基本操作

插入

语法格式

db.collection.insert(json)
db.collection.insertMany(json)
复制代码

mongodb每次可接受最大消息是48MB

删除

语法格式

db.collection.remove(query) \\ 根据条件删除文档
db.collection.remove() \\ 删除所有文档
db.collection.drop() \\ 清空集合所有文档
复制代码

更新

更新操作是不可分割:若是两个更新同时发省,先到达服务器的先智星,接着执行另外一个。

语法格式

db.collection.update(query, json, option1, option2)
复制代码

调用update时,数据库会查找第一个符合query条件的文档,找到后使用json覆盖更新文档,更新后文档的位置和_id都会发生改变。

  • option1 值为true和false,若找到文档就更新,找不到就不更新。
  • option2 值为true和false,要更新查询到的多个文档

修改器

语法格式

db.collection.update(query, {key: {updateCommand:value}})
复制代码
  • $inc 仅用于数字类型,对原始值加上value得到新的值,要是原始值不存在,创建新值

  • $set 用来指定一个字段的值,如果这个字段不存在,则创建它。

  • push会向数字末尾加入一个元素,要是没有创建新数组,push 会向数字末尾加入一个元素,要是没有创建新数组,each结合$push后可实现将多个值添加到数组。

    {"$push": {key: {"$each": [value1, ... , valueN]}}}
    复制代码
  • $slice 用来指定数组的最大长度,小于最大长度全部保留,大于最大长度,保留最新的N个元素。

    {"$push": {key: {"$slice": 10}}}
    复制代码
  • $addToSet 用来向数组添加唯一值

    {"$addToSet": {key: value}}
    {"$addToSet": {key: {"$each": [value1, ... , valueN]}}}
    复制代码
  • $pop 删除数组一端一个元素

    {"$pop": {key: 1}} 从数组末端
    {"$pop": {key: -1}} 从数组头部删除一个
    复制代码
  • $pull 根据条件删除一个元素

    {"$pull": {"key": "value"}}
    复制代码
  • 位置修改器,即使用修改和数组位置对某个元素进行修改

    db.collection.update({"post": value}, {"$inc": {"property.index.property": value}} \\ 使用具体位置进行修改
    db.collection.update({"comments.author": "John"}, {"$set": {"comments.$.author": "Jim"}}) \\ 查询数据进行修改
    复制代码
  • $setOnInset 用于创建文档时赋值,之后不在更新。

    db.collection.update({}, {"$setOnInsert": {"key": value}}, true)
    复制代码

修改器速度

由于MongoDB采用依次插入文档,原始文档修改后有可能造成原来的位置无法存放修改后的文档,这个文档就会被移动集合中的另一个位置,故会影响文档的修改速度。

MongoDB为了检测以上变化,它为每个集合设置了一个默认新文档增长空间,默认为1,即填充因子。每次移动文档,MongoDB都会动态的调整填充因子的值,从而为新文档预留增长空间。如果不在移动文档,填充因子值会慢慢降低,从而趋近于1.

如果会频繁使用插入、更新、删除操作,可以使用usePowerOf2Sizes选项来提高磁盘复用率.

返回更新后文档

findAndModify 返回更新后的文档。

语法格式

do.runCommand({"findAndModify": "collectionName", "query": {}, "sort": {}, remove: true|false}, "new": true|false, fields:[], upsert: true|false, update: {})
复制代码
  • findAndModify 字符串,集合名
  • query 查询条件
  • sort 排序条件
  • update 修改文档
  • remove 删除文档
  • new 默认返回更新前
  • fields 文档中需要返回的字段
  • upsert 表示文档存在更新,不存在添加

查询

语法格式

db.collection.find(condition, fields)
复制代码
  • condition 可选,无参默认查询collection集合中的所有文档,有参查询限定条件的所有文档

  • 多个键值对之间的关系用默认使用 AND 进行表示

    db.blog.find({"author": "yangxiao", "age": 12})
    复制代码
  • fields 表示需要返回的键或排除需要返回的键,_id在不排除的情况下每次都返回

    db.blog.find({}, {"author": 1, "age": 1, "weather": 0})
    复制代码
  • 传递给数据库查询的文档值必须是常量

条件

  • $lt 小于
  • $lte 小于等于
  • $gt 大于
  • $gte 大于等于
  • $ne 不相等

如查询年龄大于25小于30用户

db.users.find({"age": {"$gt": 25, "$lt": 30}})
复制代码

OR 查询

MongoDB有两种方式进行 OR 查询:

  • $in 用来查询一个键的多个值
  • $nin 用来查询一个键不含有的多个值
  • $or 在多个键中查询任意的给定值
db.user.find({"age": {"$in": [18, 20, 22]}})
db.user.find({"age": {"$nin": [19]}})
db.user.find({"$or": [{"age": 10}, {"username": "haha"}]}) \\ 查询用户年龄为10岁或用户名为haha的用户
复制代码

元操作符

$not是元条件句,即可以用在任何其他条件之上。

$mod 会使用查询值除以第一个给定值,若余数等于第二个给定值则匹配成功。

查询所有用户id除以5余1的所有用户

db.users.find({"id": {"$mod": [5, 1]}})
复制代码

查询所有用户id除以5余数不等于1的所有用户

db.user.find({"id": {"$not": {"$mod": [5, 1]}}})
复制代码

$not与正则表达式联合使用时即为有用,用来查找哪些与特定模式不匹配的文档。

注意

条件语句是内存文档的键,修改器是外层文档的键。有一些特殊的元操作也位于外层文档,如$and$or$nor

特定类型查询

  • null 值不仅会匹配值为null的文档,也会匹配不包含这个键的文档。

    查询用户年龄是null或不包含age属性的所有用户

    db.users.find({"age": null})
    复制代码
  • null 配合 $exists 可实现仅查询键值为null的文档

    查询用户年龄为null的所有用户

    db.users.find({"age": {"$in": [null], "$exists": true}})
    复制代码

正则表达式

使用正则表达式能够灵活有效匹配字符串。

db.users.find({"name": /joe/i})
复制代码

注意

MongoDB 可以为前缀型正则表达式查询创建索引,所以这种类型的查询会非常高效。

数组

查询水果中的苹果

db.food.insert({"fruit": ["apple", "banana", "peach"]})
db.food.find({"fruit": "apple"})
复制代码
  • $all 匹配数组中的多个元素

    db.food.find({"fruit": {"$all": ["apple", "banana"]}})
    复制代码
  • 查询特定位置的数组元素

    db.food.find({"fruit.2": "peach"})
    复制代码
  • $size 查询特定长度的数组

    db.food.find({"fruit": {"$size": 10}})
    复制代码
  • $slice 可以返回某个键匹配的数组的元素的子集

    查询一个博客文章的文档,且返回前10个评论

    db.blog.findOne({}, {"comments": {"$slice": 10}})
    复制代码

    返回后10个评论

    db.blog.findOne({}, {"comments": {"$slice": -10}})
    复制代码

    返回评论在24-33之间的数据

    db.blog.findOne({}, {"comments": {"$slice": [23, 10]}})
    复制代码
  • 返回第一个匹配的数组元素,使用$代表位置

    db.blog.find({"comments.name": "blob"}, {"comments.$": 1})
    复制代码
  • $elemMatch 使用多个条件和数组中的每个元素进行比较,在内嵌文档查询中费用有用。

    db.users.find({"age": {"$elemMatch": {"$gt": 10, "$lt": 20}}})
    db.users.find({"age": {"$gt": 10, "$lt": 20}}).min({"age": 10}).max({"age": 20})
    db.blog.find()
    {
      "content": "...",
      "comments": [
      	{
      	  "author": "joe",
      	  "score": 3,
      	  "comment": "nice post"
      	}
      ]
    }
    db.blog.find({"commets": {"$elemMatch":{"author": "joe", "score": {"$gte": 5}}}})
    复制代码
  • $where值是一个function函数,由于在服务器端执行脚本是非常危险的使用,可以使用--noscripting选项禁用。

游标

数据库使用游标返回find的执行结果。

客户端对游标的实现通常能够对最终结果进行有效的控制。

var cursor = db.collection.find();
while (cursor.hasNext()) {
  obj = cursor.next();
}
cursor.forEach(function(x)) {
  pirnt(x)
}
复制代码

limit 、skip 和 sort

  • limit 限制返回结果的数量
  • skip 跳过多个文档,返回剩余文档
  • sort 接受一个对象作为参数,对数据进行排序。排序方向使用数字1(升序)或-1(降序)表示。

注意

避免使用skip略过大量数据,因为要先找到需要被略过的数据,然后再抛弃这些数据。故速度会很慢。

优先级

MongoDB处理不同类型数据是有一定顺序的。

最小值 < null < 数字 < 字符串 < 对象/文档 < 数组 < 二进制数据 < 对象ID < 布尔型 < 日期型 < 时间戳 < 正则表达式 < 最大值 
复制代码

索引

不使用索引的查询成为全表扫描。

创建索引语法格式

db.collection.ensureIndex({"key": 1 | -1})
复制代码
  • key 表示键
  • 1 或 -1 表示索引是升序还是降序
  • 索引在创建时会更具文档数量多少从而消费时间不同,可以使用db.currentOp()检查创建进度。

索引的本质是树,最小值在最左边的叶子上,最大值在最右边的叶子上。且尽量让索引使用右平衡,这样可以保证最新的数据在内存中。

MongoDB支持基本索引、符合索引、唯一索引、稀疏索引、覆盖索引等一系列索引。

MonngoDB创建索引会暂停所有的读写请求,可以在创建索引时指定background选项。

复合索引

在多个键上建立索引就是复合索引。

  • 使用复合索引注意多个键排序方向要密切贴合日常查询数据时哪个方向比较多,只有这样的索引才能极大的提高查询效率。
  • 单一索引无须注意方向,因为MongoDB在排序时可以从索引的相反方向获取数据。
  • 当一个索引包含用户请求的所有字段,可以认为这个索引覆盖了本次查询——覆盖索引。在实际中,应该优先使用覆盖索引,而不是获取实际文档,即获取有索引的所有字段。
  • 前缀索引也叫隐式索引,这样的索引无需用户主动创建,MongoDB在查询过程中会主动将前缀索引转换为隐式索引来提高查询效率。如将email地址反向存储就可以极大提高效率。
  • 设计复合索引时应该将会用于精确匹配的字段放在索引前面,将用于范围匹配的字段放在最后。
  • $or 在查询具有多个索引的字段上时,所有索引都可以使用。因为MongoDB对每个索引查询,最后合并结果集,由于需要执行多次查询,效率并不高,应尽可能使用$in
    • $in 查询时无法控制返回文档的顺序
    • $or 在使用时,MongoDB需要检查每次查询的结果集并且从中移除重复文档,故效率不高。

在查过程中尽量避免使用以下操作符:

  • $where 完全无法使用索引
  • $ne 可以使用索引,但不高效
  • $not 有时可以使用索引,通常情况下都会退化为全表扫描
  • $nin 总是进行全表扫描

注意

MongoDB 在3.x之前每次查询只能使用一个索引,$or例外。

索引嵌套文档和数组

嵌套文档

语法格式

db.collection.ensureIndex({"property.property": 1 | -1})
复制代码

嵌套文档的索引的创建方式和一般索引的创建方式相同。

注意

对嵌套文档创建索引和嵌套文档中的字段创建索引是不同。

数组

对数组创建索引实际上是对数组中每个元素建立一个索引条目,由于需要给数组的每一个元素创建索引,所以对数组进行插入、更新或删除都需要进行更新索引,故在数组上创建索引比在单值上创建索引代价高。

MongoDB支持给数组的中的某个元素创建索引。

一个索引中数组字段最多只能有一个。这是为了避免多键索引中索引条目爆炸性增长,即每一对可能的元素都要索引,这样导致每个文档拥有n*m个索引。

对于某个索引的键,如果这个键在某个文档中是一个数组,那么这个索引就会被记为多键索引。索引一旦被标记为多键索引,就无法再变成非多键索引,唯一的办法就是删除重建。多键索引可能会比非多键索引慢一些。

索引基数

基数就是集合中某个字段拥有不同值得数量。如性别做为键,仅拥有三种可能得值,即男、女、未知,这种键得基数就非常低;若用户名作为键,每个文档就拥有不同得值,这类得键基数就非常高。

通常,一个字段得基数越高,这个键上索引就越有用。这是因为索引能够迅速将搜索范围缩小到一个比较小的结果集。对于基数低的字段,索引通常无法排除掉大量可能的匹配。

一般来说,应该在基数比较高的键上建立索引,或者至少应该把基数较高的键放在复合索引的前面。

explain() 和 hint()

explian() 函数用来解析本次查询计划

  • cursor 使用的索引名称
  • isMultiKey 是否使用了多键索引
  • n 本次查询返回的文档数量
  • nscannedObjects 按照索引指针去磁盘上查找实际文档次数。
  • nscanned 查找过文档数量
  • scanAndOrder 是否在内存中对结果集进行了排序
  • indexOnly 是否只使用索引就能完成此次查询
  • nYields 为了让写入请求能够顺利进行,本次查询暂停次数
  • millis 本次查询耗费时间,单位毫秒
  • indexBounds 描述索引的使用情况,给出了索引的遍历范围

hint() 强制本次查询使用哪个索引

MongoDB在对一个集合使用索引查询一次后,会将本次的查询计划保存下来,在接下来的相同查询中使用该查询计划。若MongoDB发现数据发生了比较大的变化,MongoDB会重新评估查询计划,并缓存。

使用索引的查询流程:

  • 先根据条件在索引中查询符合条件的索引
  • 根据符合条件索引查询对应的文档

故使用索引查询数据要经历两次。

何时不应该使用索引

提取较小的子数据集时,索引比较高效。

结果集在原集合中所占比例越大,索引的速度就越慢,因为使用索引需要进行两次查找。

一般来说,如果查询需要返回集合内30%文档(或者更多),哪就应该对索引和全表扫描的速度进行比较。然而这个数字可能会在2%~60%之间变动。

影响索引效率的属性

索引通常适用情况

  • 集合较大
  • 文档较大
  • 选择性查询

全表扫描通常适用的情况

  • 集合较小
  • 文档较小
  • 非选择性查询

$natural 自然排序操作符,若时一个表只有插入操作,且要查询最新结果集时,使用该方法进行排序获取数据就非常有用,若是频繁更新的文档,该方法就无效了。

类型

索引大小是有限制的,如果一个记录超出了它的限制,就不会出现在索引条目中。所以所有字段都必须小于1024字节,才能包含到索引里。超出8KB大小键不会受到唯一索引的约束,即可以插入多个同样的8KB长的字符串。

MongoDB支持以下索引类型

  • 唯一索引

  • 复合唯一索引

    db.collection.ensureIndex({"age": 1}, {"unique": true, "dropDups": true})
    复制代码
  • 稀疏索引——是为了解决索引只包含对应键的文档生效,无对应键的文档不创建索引。

    db.collection.ensureIndex({"email": 1}, {"unique": true, "sparse": true})
    复制代码

    MongoDB使用sparse选项创建稀疏索引。

  • 全文本索引

  • 地理空间索引

管理

所有数据库的索引信息都存储在system。indexes集合中。这是一个保留集合,不能在其中插入或删除文档,只能通过ensureIndex或者dropIndexes对其进行操作。

固定集合

MongoDB中的“普通”集合时动态创建的,而且可以自动增长以容纳更多的数据。

MongoDB中的固定集合是指集合的大小是固定,若固定集合的已经没有空间了,最老的文档就会被删除以释放空间,从而插入新的文档。

固定集合创建方法如下

db.createCollectionn(name, options)
复制代码
  • name 集合名称
  • options 描述集合的特点
    • capped 是固定集合
    • size 固定集合的大小,单位字节
    • max 固定最多可插入多少文档

普通集合转换为固定集合的方法如下

db.runCommand({"convertToCapped": "collectionName", "size": number})
复制代码

固定集合数据写入顺序与大多数集合不一样:数据被顺序的写入磁盘空间,故写入速度很快。因此,使用固定集合存储日志类型的业务数据特别合适。缺点是无法控制什么时候数据被覆盖。

固定集合一旦创建,无法更改为动态集合。

为固定集合指定文档数量限制时,必须同时指定固定集合大小。不管达到哪个限制,之后新插入的文档就会把最老的文档挤出集合。

对固定集合可进行一种特殊的排序,称为自然排序。自然排序恰恰返回结果集中文档的顺序就是文档在磁盘上的顺序。对普通集合,自然排序意义不大,因为文档位置经常变动。自然排序默认得到的文档是从旧到新排列的,也可以按照从新到旧的顺序排列获取数据。

固定集合中可以使用循环游标。

循环游标是一种特殊的游标,当循环游标的结果集被取光后,游标不会关闭,继续等待新的文档插入后取新的数据。如果超过10分钟没有新文档插入,数据库就会释放循环游标。

没有_id索引的集合

默认情况下,每个集合都有一个"_id"索引。但是,如果在调用createCollection创建集合时指定autoIndexId选项为false,创建集合时就不会自动在"_id"上创建索引。且"_id"索引必须是唯一索引。

注意

如果创建了一个没有"_id"索引的集合,哪就永远不能复制它所在的mongod了。复制操作要求每个集合上都要有"_id"索引(对于复制操作,能够唯一标识集合中的每一个文档是非常重要的)

TTL索引

TTL指time-to-live index,即具有生命周期的索引,这种索引允许为每个文档设置一个超时时间。

使用expireAfterSecs选项创建一个多少秒自动过期的索引。

MongoDB每分钟对TTL索引进行一次清理,索引不应该依赖秒为单位的时间保证索引的存活状态。

TTL索引不能是符合索引,但是可以像“普通”索引一样来优化查询。

全文本索引

MongoDB有一个特殊类型的索引用于在文档中搜索文本,叫全文本索引。全文本索引会对文本进行分词,从而提取关键字作为索引,故一个文本会生成至少一条索引。由于文本要被分解、分词,故全文本索引有非常严重的性能问题,故全文本索引应在离线状态下创建。也会降低分片时的数据迁移速度,因为数据迁移到其他分片时,所有的文本都需要重新进行索引。

地理空间索引

GridFS

聚合

聚合框架

使用聚合框架可以对集合中的文档进行变换和组合。基本上,可以使用多个构件创建一个管道,用于对一连串的文档进行处理。这些构件包括筛选、投射、分组、排序、限制和跳过

语法格式如下

db.collection.aggregate(filter, fields, group, sort, limit, skip)
复制代码

管道操作符

每个操作符都会接受一连串的文档,对这些文档做一些类型转换,最后将转换后的文档作为结果传递给下一个操作符(对于最后一个管道操作符,是将结果返回给客户端)。

不同的管道操作可以按任意顺序组合一起使用,而且可以被重复多次。

所有管道操作符如下

$match

$match用于对文档集合进行筛选,但不能在其中使用地理空间操作符。

尽可能将$match放在管道最前面。

$project

project可以从文档中提取字段,也可以对提取的字段进行重命名(`{"project": {"user_id": "$_id"}}`)。

重命名方法

{"$project": {"newFieldName": "$originalFieldName"}}
复制代码

使用简单表达式将多个字面量和变量组合一起使用或者组合任意深度的嵌套使用。

使用数学表达式,语法格式如下

{"$project": {"fieldName": {"$add": ["$originalFieldName1", "$originalFieldName2"]}}}
复制代码
  • $add : [expr1, expr2, ... , exprN] 这个操作符接受一个或多个表达式参数,将这些参数相加。
  • $subtract: [expr1, expr2] 接受两个表达式作为参数,用第一个减去第二个
  • $multiply : [expr1, expr2, ... , exprN] 这个操作符接受一个或多个表达式参数,将这些参数相乘。
  • $divide : [expr1, expr2] 接受两个表达式作为参数,用第一个除以第二个商作为结果。
  • $mod : [expr1, expr2] 接受两个表达式作为参数,用第一个除以第二个余数作为结果。

使用日期表达式,语法格式如下

{"$project": {"fieldname", {"$month": "$originalfieldname"}}}
复制代码
  • $year 从日期中提取年
  • $month 从日期中提取月
  • $week 从日期中提取周
  • $dayOfMonth 从日期中提取天
  • $dayOfWeek 从日期中提取周
  • $dayOfYear 从日期中提取天
  • $hour
  • $minute
  • $second

使用字符串表达式,语法格式如下:

{"$project": {"fieldname": {"$concat": [$originalfieldname1, $originalfieldname2]}}}
复制代码
  • "$substr": [expr, startOffset, numToReturn]

    expr表示必须是字符串,这个操作会截取这个字符串的字串第startOffset字节开始的numToReturn字节。

  • "$concat" : [expr1, expr2, ... , expr2] 将给定的表达式连接在一起作为返回结果

  • "$toLower": expr 将字符串转换为小写

  • "$toUpper": expr 将字符串转换为大写

使用逻辑表达式,语法格式如下

  • "$cmp": [expr1, expr2]

    比较expr1和expr2。如果expr1等于expr2,返回0;如果expr1 < expr2,返回一个负数;如果expr1 > expr2,返回一个正数。

  • "$strcasecmp": [expr1, expr2]

    比较string1和string2,区分大小写。只对罗马字符组成字符串有效。

  • "eq"/"eq"/"ne"/"gt"/"gt"/"gte"/"lt"/"lt"/"lte": [expr1, expr2]

    对expr1和expr2执行相应的比较操作,返回比较结果。

  • 布尔表达式

    • "$and": [expr1, expr2, ... , exprN]

      如果所有表达式的值都是true,则返回true,否则false

    • "$or": [expr1, expr2, ... , exprN]

      只要有任意表达式的值为true,则为true,否则false

    • "$not": expr 对expr取反

  • 控制语句

    • "$cond": [booleanExpr, trueExpr, falseExpr]
      • 如果booleanExpr的值为true,就返回trueExpre,否则返回falseExpr
    • "$isNull": [expr, replacementExpr]
      • 如果expr是null,返回replacementExpr,否则返回expr

如果有个教授想通过某种比较复杂的计算为学生打分:出勤率占10%,日常测验成绩30%,期末考试占60%

db.sutdents.aggregate({
  "$project": {
  	"grade": {
  	  "$cond": {
  	    "$teachersPet", 100, {
  	      "$add": [
  	        { "$multiply": [.1, "$attendanceAvg"] },
  	        { "$multiply": [.3, "$quizzAvg"]},
  	        { "$multiply": [.6, "$testAvg"]}
  	      ]
  	    }
  	  }
  	}
  }
})
复制代码

$group

$group操作可以将文档依据特定字段的不同值进行分组

  • 算数操作符

    • $sum : value 分组求和
    • $avg: value 分组求平均值
  • 极值操作符

    • $max: value 最大值
    • $min: value 最小值
    • first:value返回第一个值,有序求最大值和最小值使用first: value 返回第一个值, 有序求最大值和最小值使用first和$last
    • $last: value 返回最后一个值
  • 数组操作符

    • $addToSet : expr如果当前数组不包含expr,哪就将它添加到数组中。在返回结果集中,每个元素最多出现一次。
    • $push : expr 不管什么值,都添加到数组

$unwind

拆分可以将数组中每一个值拆分为单独文档。

$sort

排序

$limit

返回多少个结果集

$skip

跳过多少结果集

如何使用管道

应该尽量在管道开始阶段执行projectproject、group、$unwind操作,就将尽可能多的文档和字段过滤掉。

管道如果不是从原先集合中使用数据,哪就无法在筛选和排序中使用索引。如果可能,聚合管道会尝试对操作进行排序,以便能够有效使用索引。

MongoDB不允许单一聚合操作占用过多内存,如果发现某个聚合操作占用20%以上的内存,则直接报错。

MapReduce

MapReduce是聚合工具中的明星,它非常强大、非常灵活。

有些问题过于复杂,无法使用聚合框架的查询语言来表达,这时就可以使用MapReduce。

MapReduce使用Javascript作为“查询语言”,因此它能够表达任意复杂的逻辑。然而,这种强大是有代价的:MapReduce非常慢,不应该用在实时的数据分析中。

MapReduce能够在多台服务器之间并行执行。它会将一个大问题拆分为多个小问题,将各个小问题发送到不同机器上,每台机器只负责完成一部分。所有机器完成时,再将这些零碎的的解决方案合并为一个完整的解决方案。

MapReduce需要几个步骤。最开始是映射,将操作映射到集合中的每个文档。这个操作要么“无作为”,要么“产生一些键和X个值”。然后就是中间环节,称作洗牌,按照键分组,并将产生的键值组成列表放到对应的键中。化简则把列表的值简化成一个单值。这个值被返回,然后接着进行洗牌,直到每个键的列表只有一个值为止,这个值也就是最终结果。

使用方法如下

map = function () {
  for (var key in this) {
      emit(key, {count : 1});
  }
}
reduce = function (key, emits) {
    total = 0;
    for (var i in emits) {
        total += emits[i].count;
    }
    return {"count": total}
}
mr = db.runCommand({"mapreduce": "foor", "map": map, "reduce": reduce});
复制代码

MapReduce的连接关闭后它就自动删除了。

MapReduce键的含义如下:

  • "finalize": function

    可以将reduce的结果发送给这个键,这是整个处理过程的最后一步。

  • "keeptemp": boolean

    如果值为true,那么在连接关闭时会将临时结果集合保存下来,否则不保存。

  • out: string

    输出集合的名称。如果设置了这个选项,系统会自动设置keeptemp:true

  • query: document

    在发送map函数前,先用指定条件过滤文档。

  • sort:document

    在发送map函数前,先用指定条件过滤文档。

  • limit: integer

    发送map函数的文档数量的上限。

  • scope : document

    可以在JavaScript代码中使用变量

  • verbose: boolean

    是否记录详细的服务器日志

聚合命令

  • count()

  • distinct()

  • group

    db.runCommand({
      "group": {
        "ns": "collectionName",
        "key": "day",
        "inital": {"time": 0},
        "$reduce": function(doc, prev) {
          if (doc.time > prev.time) {
            prev.price = doc.price;
            prev.time = doc.time;
          }
        },
        "$finalize": function(prev) {
          var mostPopular = 0;
          for (i in prev.tags) {
            if (prev.tags[i] > mostPopular) {
              prev.tag = i;
              mostPopular = perv.tags[i];
            }
          }
          delete prev.tags;
        }
      }
    })
    复制代码
    • ns 指定要分组的集合名称

    • key 分组的键

    • $keyf 函数作为键

    • initial

      每一组reduce函数调用中的初始time值,会作为锤石文档传递给后续过程,每一组的所有成员都会使用这个累加器,所以它的任何变化都可以保存下来。

    • "$reduce": function(doc, prev) { ... }

      这个函数会在集合内的每个文档上执行。

      doc表示当前文档和累加器文档

参考说明

  • MongoDB权威指南(第二版)

文章分类
后端
文章标签