MongoDB基础1

 

MongoDB基础1

API:

https://docs.mongodb.com/v4.0/reference/operator/query/expr/

 

教程:

https://www.bilibili.com/video/BV11b41127Lg?p=1

https://www.bilibili.com/video/BV1Rt41117mT?p=1

 

MongoDB简介

是什么?

NoSQL简介

为什么需要NoSQL

NoSQL优缺点

基本概念

规则

数据类型

增删改

条件查询

正则查询、数组查询、内嵌查询

统计、排序

游标

聚合框架

简介

聚合框架的HelloWorld

管道操作符

$match

$project

$group

$unwind

$sort

常见聚合函数

MapReduce

MapReduceHelloWorld

MapReduce的更多可选键

聚合命令

group

理解文档存储机制

索引

固定集合

GridFS

简介

优缺点

存储文件

 

 

 

MongoDB简介

是什么?

MongoDB是一个使用C++编写的、开源的、面向文档的NOSQL数据库,也是当前最热门的NoSQL数据库之一。

 

 

NoSQL简介

NoSQL的意思是“不仅仅是SQL”,是目前流行的“非关系型数据库”的统称。常见的NoSQL数据库如:Redis、CouchDB、MongoDB、Hbase、Cassandra等。

 

 

为什么需要NoSQL

简单的说,就是为了解决web2.0时代,出现的三高要求:

1、对数据库高并发读写需求

2、对海量数据的高效率存储和访问需求

3、对数据库的高扩展和高可利用的需求

 

NoSQL优缺点

 

基本概念

 

规则

 

数据类型

增删改

# mongoDB只允许一个文档最大为16M,可以使用以下命令查看文档大小
Object.bsonsize({"name":"毛毛"})
# 输出
20  # 即20个字节

var x = {"name":"keke","sex":1}
Object.bsonsize(x)

# 查看状态
db.stats()
db.xinhua.stats()

# 查看所有文档
db.col.find()

# 查看集合中第一个文档
db.col.findOne({条件对象})

# 文档替换,这是替换整个文档,不常用
db.col.update(条件,新的文档)
db.xuser.update({"userId":"s0","course":"课程0"},{"userId":"s0","course":"课程0","score":99})

# 更新修改器,用来做复杂的更新操作
db.col.update(条件,{"$set":{}})
db.xuser.update({"age":22},{"$set":{"work":"cxy"}},0,1) 
# 参数0表示upset:找到符合条件的文档就更新,否则会以这个条件来更新文档来创建新文档
# 指定update方法的第三个参数为true,可表示是upset
# 参数1表示改条件的文档全量更新

db.xuser.update({"name":"u2"},{"$inc":{"age":5}})

db.xuser.update({"name":"毛毛"},{"$push":{"like":"play"}})
db.xuser.update({"name":"毛毛"},{"$push":{"like":{"$each":["sleep","sport"]}}})

# slice是和$push $each一起使用的
db.xuser.update({"name":"毛毛"},{"$push":{"like":{"$each":["LOL","study"],"$slice":-3}}})

# $是按照索引来取,把毛毛的like数据里的第2个元素改为"AVM"
db.xuser.update({"like.2":"sport","name":"毛毛"},{"$set":{"like.$":"AVM"}})

# save如果存在就修改,不存在就新增。insert只能插入不重复_id的文档。

 

条件查询

db.col.find
function (query, fields, limit, skip, batchSize, options) {
    ...
    return cursor;
}

# 查询年龄大于20岁
db.xuser.find({"age":{"$gt":20}})

# 投影,过滤id字段,只取3条
db.xuser.find({},{"_id":0},3)

# 大于20小于27岁,默认是and条件(隐式)
db.xuser.find({"age":{"$gt":20,"$lt":27}})
db.xuser.find({"name":{"$ne":"u1"}}) # 名称不为“u1”

# 显式指明,数组内关系为and或or
db.xuser.find({"$and":[{name:"u3"},{"age":{"$gt":20}}]})
db.xuser.find({"$or":[{name:"u3"},{"age":{"$gt":20}}]})

# $not不能最为顶级操作符,且其后只能跟document或正则
错误写法:db.xuser.find({"$not":{"name":"u1"}}) 
错误写法:db.xuser.find({"name":{"$not":"u1"}})
跟正则:db.xuser.find({"name":{"$not":/u1/}})  # 名称不为u1
跟document:db.xuser.find({"age":{"$not":{"$gt":25}}}) # 年龄不大于25岁

# $mod拿age和100求余,余数为2的文档
db.xuser.find({"age":{"$mod":[20,2]}})

# $in 年龄不在22、23的文档
db.xuser.find({"age":{"$not":{"$in":[22,23]}}})
db.xuser.find({"age":{"$nin":[22,23]}})

# $all 数组内必须包含LOL和study的文档
db.xuser.find({"like":{"$all":["LOL","study"]}})

# $exists 查询必须存在like键的文档
db.xuser.find({"like":{"$exists":1}})

# null 不仅能查不存在键work的文档,还能查键work为null的文档
db.xuser.find({"work":null})
db.xuser.find({"work":{"$in":[null],"$exists":1}})

正则查询、数组查询、内嵌查询

# 正则查询
db.xuser.find({"name":/^u/})

# 单个元素匹配
db.xuser.find({"name":"u1"})

# 多个元素匹配
db.xuser.find({"like":{$all:["LOL","study"]}})

# 可以使用索引指定查询数组特定位置,{"key.索引号":"value"}
db.xuser.find({"like.0":"AV"})

 

# 如果内嵌是个数组,则x.y中y就是索引,如果内嵌是个文档,则x.y中y就是key

统计、排序

# 使用count()函数是统计find({})的结果,不受其他过滤条件影响
>db.xuser.find().limit(3).count()
4
>db.xuser.find().limit(3).count(true)
3

游标

 

聚合框架

简介

MongoDB的聚合框架,主要用来对集合中的文档进行变换和组合,从而对数据进行分析以加以利用。

聚合框架的基本思路是:采用多个构件来创建一个管道,用于对一连串的文档进行处理。这些构建包括:筛选(filtering)、投影(projecting)、分组(grouping)、排序(sorting)、限制(limiting)和跳过(skipping)。

使用聚合框架的方式

db.集合.aggregate(构件1,构件2...)
# 注意由于聚合的结果要返回客户端,因此聚合结果必须限制在16M以内,这是MongoDB支持
# 最大响应消息的大小

聚合框架的HelloWorld

# 插入预备数据:
for(var i=0;i<100;i++) {
    for(var j=0;j<4;j++) {
        db.xuser.insert({"userId":"s"+i,"course":"课程"+j,"score":Math.random()*100})
        }
    }
# 1、找到所有考了80分以上的学生,不区分课程
# {"$match":{"score":{"$gte":80}}}

db.getCollection('xuser').aggregate([{"$match":{"score":{"$gte":80}}}])

# 2、将每个学生的名字投影出来,注意顺序,如果$project放在前面查询
# 结果为空,因为这是一个管道,按照顺序过滤
# {"$project":{"userId":1}}
db.getCollection('xuser').aggregate([
{"$match":{"score":{"$gte":80}}},
{"$project":{"userId":1}}
])

# 3、对学生名字进行排序,某个学生的名字出现一次,就给他加1,注意_id是必须的,其value就是你要分组的字段
# {"$group":{"_id":"$userId","count":{"$sum":1}}}
db.getCollection('xuser').aggregate([
{"$match":{"score":{"$gte":80}}},
{"$project":{"userId":1}},
{"$group":{"_id":"$userId","count":{"$sum":1}}}
])

# 4、对结果集分数的降序进行排列
# {"$sort":{"count":-1}}
db.getCollection('xuser').aggregate([
{"$match":{"score":{"$gte":80}}},
{"$project":{"userId":1}},
{"$group":{"_id":"$userId","count":{"$sum":1}}},
{"$sort":{"count":-1}}
])

# 5、限制limit
# ${limit:5}
db.getCollection('xuser').aggregate([
{"$match":{"score":{"$gte":80}}},
{"$project":{"userId":1}},
{"$group":{"_id":"$userId","count":{"$sum":1}}},
{"$sort":{"count":-1}},
{"$limit":5}
])

聚合操作每一个步骤的输入都是上一个步骤的输出。

 

 

管道操作符

每个操作符接受一系列的文档,对这些文档做相应的处理,然后把转换后的文档作为结果传递给下一个操作符。最后一个操作符会将结果返回。

不同的管道操作符,可以按照任意顺序,任意个数组合在一起使用。如上$project、$sort、$group等,注意:不同的顺序可能会直接影响最终的结果

 

$match

用于对文档集合进行筛选,里面可以使用所有常规的查询操作符。通常会放置在管道最前面的位置,理由如下:

1、快速将不需要的文档过滤,减少后续操作的数据量

2、在投影和分组之前做筛选,查询可以使用索引。

 

 

$project

project放在$match之前,则match里的过滤条件必须包含在project里,否则查不到任何内容

用来从文档中提取字段,可以指定包含和排除字段,也可以重命名字段,不写默认取所有。比如要将studentId改为sid,如下:

db.sources.aggreate([{"$project":{"sid":"$studentId"}}]})

管道操作符还可以使用表达式,以满足更复杂的需求。

 $project的数学表达式

支持的操作符和语法:

$add:[expr1[,expr2,..exprn]]
$subtract:[expr1,expr2]
$multiply:[expr1[,expr2,...exprn]]
$divice:[expr1,expr2]
$mod:[expr1,expr2]

# 例如给成绩集体加20分
{"$project":{"newScore":{"$add":["$score":20]}}}

 $project的日期表达式

聚合框架包含了一些用于提取日期信息的表达式,如下:

# 注意:这些只能操作日期型的字段,不能操作数据,用法案例
{"$project":{"opeDay":{"$dayOfMonth":"$recoredTime"}}} # 从redoredTime字段取出月重命名为opeDay

 $project的字符串表达式

$substr:[expr,开始位置,要取的字节个数]
$concat:[expr1[,expr2,...exprn]]
$toLower:expr
$toUpper:expr
# 例如:{"$project":{"sid":{"$concat":["$studentId","cc"]}}}}

 $project的逻辑表达式

$cmp:[expr1,expr2]  #比较两个表达式,0表示相等,正数前面的大,负数后面的大
$strcasecmp:[string1,string2] # 比较两个字符串,区分大小写,只对由罗马字符[I,II,III,IV,VI]组成的字符串有效
$eq $ne $gt $gte $lt $lte:[expr1,expr2]
$cond:[booleanExpr,trueExpr,falseExpr]:#三目运算符。如果boolean表达式为true,返回true表达式,否则返回false表达式。
$ifNull:[expr,otherExpr]:#如果expr为null,返回otherExpr,否则返回expr
# 例如
db.scores.aggregate([{"$project":{"newScore":["$cmp":["$studentId","sss"]]}}])

 

$group

用来将文档依据特定字段的不同值进行分组。选定了分组字段过后,就可以把这些字段传递给$group函数的"_id"字段了。例如:

db.score.aggregate({"$group":{"_id":"$studentId"}}) 或者
db.score.aggregate({"$group":{"_id":{"sid":"studentId","score":"$score"}}})

$group支持的操作符:

$sum:value  #对于每个文档,将value与计算结果相加
$avg:value #返回每个分组的平均值
$max:expr #返回分组内的最大值
$min:expr #返回分组内的最小值
$first:expr #返回分组的第一个值,忽略其他值,一般只有排序后,明确知道数据顺序的时候,此操作才有意义
$last:expr #与上面一个相反,返回分组的最后一个值
$addToSet:expr # 如果当前数组中不包括expr,那就将它加入到数组中
$push:expr # 把expr加入到数组中

# 案例
db.getCollection('xuser').aggregate([{"$group":{"_id":"$userId","avg":{"$avg":"$score"}}}])

$unwind

用来把数组中的每个值拆分成为单独的文档。

db.getCollection('xuser').insert({"userId":"小米","like":["apple","play","eat"]})

db.getCollection('xuser').aggregate([{"$unwind":"$like"}])

# 返回结果
/* 1 */
{
    "_id" : ObjectId("5ef98dc0388090bf5b20fc4a"),
    "userId" : "小米",
    "like" : "apple"
}

/* 2 */
{
    "_id" : ObjectId("5ef98dc0388090bf5b20fc4a"),
    "userId" : "小米",
    "like" : "play"
}

/* 3 */
{
    "_id" : ObjectId("5ef98dc0388090bf5b20fc4a"),
    "userId" : "小米",
    "like" : "eat"
}

$sort

可以根据任何字段进行排序,与普通查询中的语法相同。如果要对大量的文档进行排序,强烈建议在管道的第一阶段进行排序,这时可以使用索引。

 

 

常见聚合函数

count

用于返回集合中文档的数量,不能跟着aggregate后

db.col.find().count()

distinct

找出给定键的所有不同值,使用时必须指定集合和键,例如:

db.runCommand({"distinct":"users","key":"userId"})

MapReduce

 

# 语法
db.collection.mapReduce(
   function() {emit(key,value);},  //map 函数
   function(key,values) {return reduceFunction},   //reduce 函数
   {
      out: collection,
      query: document,
      sort: document,
      limit: number
   }
)

# 执行结果参数
result:储存结果的collection的名字,这是个临时集合,MapReduce的连接关闭后自动就被删除了。
timeMillis:执行花费的时间,毫秒为单位
input:满足条件被发送到map函数的文档个数
emit:在map函数中emit被调用的次数,也就是所有集合中的数据总量
ouput:结果集合中的文档个数(count对调试非常有帮助),out: { inline: 1 }设置了 {inline:1} 将不会创建集合,整个 Map/Reduce 的操作将会在内存中进行。注意,这个选项只有在结果集单个文档大小在16MB限制范围内时才有效。
ok:是否成功,成功为1
err:如果失败,这里可以有失败原因,不过从经验上来看,原因比较模糊,作用不大

 

 

MongoDB的聚合框架中,还可以使用MapReduce,它非常强大灵活,但具有一定的复杂性,专门用于实现一些复杂的聚合功能。

MongoDB中的MapReduce使用JavaScript来作为查询语言,因此能表达任意的逻辑,但是它运行的非常慢,不应该用在实时的数据分析中。

 

MapReduce的HelloWorld

# 找出集合中所有的键值,并统计每个键出现的次数。

# 1、Map函数必须使用emit函数来返回要处理的值,示例如下:
var map = function() { # map函数会对每一个文档进行处理,这里的this即指当前文档
    for(var key in this) {
        # emit里的这个key和下面reduce参数里的key是对应的,
        # emits存储的是所有的emit(key,value)里的当前key对应的value数组
        emit(key,{count:1}); 
    }
}

# 2、reduce函数需要处理Map阶段或者是前一个reduce的数据,因此reduce返回的
# 文档必须要能作为recude的第二个参数的一个元素,示例如下:
var reduce = function(key,emits) {
    var total = 0;
    for(var i in emits) {
        total += emits[i].count;
    }
    return {"count":total}
}

# 连贯起来,在shell执行如下:
db.xuser.mapReduce( 
   function() {
        for(var key in this) {
            emit(key,1); 
        }
    }, 
   function(key, values) {return Array.sum(values)}, 
  {  
     out:"post_total" 
  }
)

# 执行结果如下:
{
    "result" : "post_total",
    "timeMillis" : 20.0,
    "counts" : {
        "input" : 401,
        "emit" : 401,
        "reduce" : 100,
        "output" : 101
    }...
# input:有401个符合条件的查询,emit:在map函数中生成了5个键值对,最后使用
# reduce函数将相同的键值分为101组
        
db.post_total.find({}).limit(2)
# 执行结果:
/* 1 */
{
    "_id" : "s0",
    "value" : 4.0
}

/* 2 */
{
    "_id" : "s1",
    "value" : 4.0
}

MapReduce的更多可选键

finalize:function(key,value){return value} # 可以将reduce的结果发送到finalize,这是整个处理的最后一步
keeptemp:boolean # 是否在连接关闭的时候,保存临时结果集合
query:document # 在发送给map前对文档进行过滤
sort:document #在发送给map前对文档进行排序
limit:integer #在发送map函数的文档数量上限
scope:document #可以在javascript中使用的变量
verbose:boolean #是否记录详细的服务器日志

# 案例
db.xuser.mapReduce( 
   function() { emit(this.userId,1);}, 
   function(key, values) {return Array.sum(values)}, 
      {  
         out:"post_total" ,
          finalize:function(key,value){
              return {'mk':key,'mv':value}
              }
      }
)
      
db.post_total.find({}).limit(1)
# 返回结果:
/* 1 */
{
    "_id" : "s0",
    "value" : {
        "mk" : "s0",
        "mv" : 4.0
    }
}

聚合命令

group

这个和管道里的$group

用来对集合进行分组,分组过后,再对每一个分组内的文档进行聚合

 

 

理解文档存储机制

将文档插入到MongoDB的时候,文档是按照插入的顺序,依次在磁盘上相邻保存。因此,一个文档变大了,原来的位置要是放不下这个文档了,就需要把这个文档移动到集合的另外一个位置,通常是最后,能放下这个文档的地方。

 

 

MongoDB移动文档的时候,会自动修改集合的填充因子(padding factor),填充因子是为新文档预留的增长空间,不能手动设定填充因子。

1、填充因子开始可能是1,也就是为每个文档分配精确的空间,不预留增长空间。

2、当有文档超过而被迫移动文档的时候,填充因子可能会增大。

3、当集合不在有文档移动的时候,填充因子会慢慢变小。

 

MongoDB进行文档移动是非常慢的

移动文档的时候,MongoDB需要将文档原先占用的空间释放掉,然后将文档写入新的空间,相对费时,尤其是文档比较大,又频繁需要移动的话,会严重影响性能。

 

 

索引

 

 

# 查看索引
>db.getCollection('xuser').getIndexes()
/* 1 */
[
    {
        "v" : 1,
        "key" : {
            "_id" : 1
        },
        "name" : "_id_",
        "ns" : "xinhua.xuser"
    }
]

>db.getCollection('xuser').find({}).explain()
# https://docs.mongodb.com/v4.0/reference/explain-results/
# 创建索引,1表示升序,-1表示降序
>db.xinhua.createIndex({"name":1}) # 缺省索引名称,mongo会自动生成一个名称
/* 1 */
{
    "createdCollectionAutomatically" : false,
    "numIndexesBefore" : 1,
    "numIndexesAfter" : 2,
    "ok" : 1.0
}

>db.getCollection('xuser').getIndexes()
[
    {
        "v" : 1,
        "key" : {
            "_id" : 1
        },
        "name" : "_id_",
        "ns" : "xinhua.xuser"
    },
    {
        "v" : 1,
        "key" : {
            "name" : 1.0
        },
        "name" : "name_1",
        "ns" : "xinhua.xuser"
    }
]

>db.xinhua.createIndex({"age":1},{"name":"myAgeIndex"})
>db.getCollection('xuser').getIndexes()
[
    {
        "v" : 1,
        "key" : {
            "_id" : 1
        },
        "name" : "_id_",
        "ns" : "xinhua.xinhua"
    },
    {
        "v" : 1,
        "key" : {
            "GUNS_XINHUA_VERSION" : 1.0
        },
        "name" : "GUNS_XINHUA_VERSION_1",
        "ns" : "xinhua.xinhua"
    },
    {
        "v" : 1,
        "key" : {
            "age" : 1.0
        },
        "name" : "myAgeIndex",
        "ns" : "xinhua.xinhua"
    }
]

# 删除索引
db.xinhua.dropIndex({"name":1}) # 按照创建的字段删除索引
db.xuser.dropIndex("myAgeIndex") # 如果自己创建了索引名称,可以使用名称删

 

固定集合

MongoDB 固定集合(Capped Collections)是性能出色且有着固定大小的集合,对于大小固定,我们可以想象其就像一个环形队列,当集合空间用完后,再插入的元素就会覆盖最初始的头部的元素!一般用于限制:最新博文(只显示前10篇),最新歌曲,等设定大小,高效利用

# 创建时需指定capped和size
>db.createCollection("cappedLogCollection",{capped:true,size:10000})

# 还可以指定文档个数,加上max:1000属性:
>db.createCollection("cappedLogCollection",{capped:true,size:10000,max:1000})

 

GridFS

简介

GridFS时是MongoDB用来存储大型二进制文件的一种存储机制。特别适合用在存储一些不常改变,但是经常需要连续访问的大文件的情况。GridFS没有专门的大型分布式文件系统那么强大,如果你不是那么需要大型分布式文件系统使用GridFS还是可以的。

GridFS用于存储和恢复那些超过16M(BSON文件限制)的文件(如:图片、音频、视频等)。它会将大文件对象分割成多个小的chunk(文件片段),一般为256k/个,每个chunk将作为MongoDB的一个文档被存储在chunks集合中。

 

优缺点

 

 

存储文件

 

 

前进时,请别遗忘了身后的脚印。
原文地址:https://www.cnblogs.com/liudaihuablogs/p/13463502.html