MongoDB学习笔记
简介
MongoDB(来自于英文单词“Humongous”,中文含义为“庞大”)是可以应用于各种规模的企业、各个行业以及各类应用程序的开源数据库。作为一个适用于敏捷开发的数据库,MongoDB的数据模式可以随着应用程序的发展而灵活地更新。与此同时,它也为开发人员 提供了传统数据库的功能:二级索引,完整的查询系统以及严格一致性等等。 MongoDB能够使企业更加具有敏捷性和可扩展性,各种规模的企业都可以通过使用MongoDB来创建新的应用,提高与客户之间的工作效率,加快产品上市时间,以及降低企业成本。
MongoDB是专为可扩展性,高性能和高可用性而设计的数据库。它可以从单服务器部署扩展到大型、复杂的多数据中心架构。利用内存计算的优势,MongoDB能够提供高性能的数据读写操作。 MongoDB的本地复制和自动故障转移功能使您的应用程序具有企业级的可靠性和操作灵活性。
安装
docker
$ docker run -itd --name mongo -p 27017:27017 mongo --auth
连接
$ docker exec -it mongo mongo
MongoDB shell version v4.2.8
connecting to: mongodb://127.0.0.1:27017/?compressors=disabled&gssapiServiceName=mongodb
Implicit session: session { "id" : UUID("29aa2ba6-23cb-451e-94ba-71580a592723") }
MongoDB server version: 4.2.8
> db
test
>
基本指令CRUD
show dbs/databases 显示当前所有的数据库
show collections 显示数据库中所有的集合
use 数据库名 进入到指定的数据库中;如果数据库不存在,则会在第一次插入数据的时候创建该数据库
db 表示当前所处的数据库
插入
db.集合名称.insert() 插入
db.hh.insert({name:"wdm", url:"wdm.life"}) //
db.hh.insertOne({}) //插入1个 new 3.2
db.hh.insertOne([{},{}]) //插入多个 new 3.2
批量插入
//性能很差 I/O 太频繁
for(var i = 1; i<=20000;i++){
db.hh.insert({num:i});
}
//改进方法
var arr=[];
for (var i=1;i<=20000;i++){
arr.push({no:i})
}
db.hh.insert(arr);
查询
db.集合名称.find() 查找
db.hh.find() //查找全部
db.hh.findOne() //最多返回一个文档数据,只会返回符合条件的第一个文档数据
db.hh.find({_id:"wdm"}) //条件查找 {属性:值}
db.集合名称.find().Count() 统计文档数
$eq 等于
// 这两个方法都是等
db.hh.find({num:{$eq:500}});
db.hh.find({num:500});
$gt 大于
db.hh.find({num:{$gt:500}});
$gte 大于等于
db.hh.find({num:{$gte:500}});
$lt 小于
$lte 小于等于
db.hh.find({num:{$lt:500}});
$ne 不等于
db.hh.find({num:{$ne:50}});
大于40小于50
db.hh.find({num:{$gt:40,$lt:50}});
$lte 小于等于 前xx条数据 此处数据有特殊性。应当使用limit(x)来操作
db.hh.find({num:{$lte:10}});
db.hh.find().limit(10);
limit() 设置显示数据的上限
db.hh.find().limit(10); //只显示前10条
skip() 跳过指定数量的数据
db.hh.find().skip(10).limit(10);//显示 11-20条数据
// 分页查询 每页10条数据 当前页码no
db.hh.find().skip((no-1)*10).limit(10);//no为页码
db.hh.find().limit(10).skip((no-1)*10);//no为页码效果一样,mongodb会自动调整skip和limit的位置
pretty() 格式化显示所有文档
进阶
小于10或大于1990
db.hh.find({$or : [{num:{$lt:10}},{num:{$gt:1990}}]})
sort({属性:1/-11}) 排序;1升序,-1降序
查询只显示部分属性 1表示显示 0表示不显示
db.hh.find({},{属性:1,_id:0})s
修改
db.集合名称.update() 修改,默认情况下替换旧对的象;如果需要修改指定的属性,而不是替换旧的对象。
-
$set 修改指定的属性
{ $set : { field : value } }
-
$unset 删除指定的属性
{ $unset : { field : 1} }
-
$inc 对文档的某个值为数字型(只能为满足要求的数字)的键进行增减的操作。
{ $inc : { field : value } }
-
$push 向数组中添加一个新的元素
{ $push : { field : value } }
-
**push,只是一次可以追加多个值到一个数组字段内。
{ $pushAll : { field : value_array } }
-
$pull 从数组field内删除一个等于value值
{ $pull : { field : _value } }
-
$addToSet 向数组中添加一个新的元素,如果数组中存在了则不会添加
-
$pop 删除数组的第一个或最后一个元素
{ $pop : { field : 1 } }
- $rename 修改字段名称
{ $rename : { old_field_name : new_field_name } }
- $bit 位操作符号
{$bit : { field : {and : 5}}}
-
db.集合名称.replaceOne() 替换
db.hh.update({name:"wdm"},{url:"wdmCRUD"})// ({条件},{新对象}) 会替换旧的数据
db.hh.update(
{name:"wdm4"},
{$set:{
url:"newurl"}
})// ({条件},{$set:{属性:"值"}}) 默认修改一个
db.hh.update(
{name:"wdm4"},
{$set:{
url:"newurl"
},
{
multi: true
}
})// ({条件},{$set:{属性:"值"}}) 修改多个符合条件的属性值
db.hh.updateOne()//只修改一个 new 3.2
db.hh.updateone()//修改多个 new 3.2
删除
-
db.colloection.remove()
根据条件来删除文档,传递的条件的方式与find()一样;注意:只要符合条件都被删除
db.hh.remove({url:"newurl"}) // 默认情况下符合条件的都被删除 db.hh.remove({url:"newurl"},true) //只删除一个文档 就传递第二个参数 true db.hh.remove({}) //必须传递参数,传空参数会删除所有的文档。谨慎操作 //如果要清空所有的文档,用remove({})性能很差:一条条的删除匹配删除 db.hh.drop() //删除集合 非常快,一了百了
-
db.colloection.deleteOne()
-
db.colloection.deleteMany()
-
db.colloection.drop() 删除集合 非常快,一了百了
-
db.dropDataBase() 删除数据库
文档之前的关系
通过内嵌文档来实现
一对一
db.wifeAndHusband.insert([
{
name:"黄蓉",
hushband:{
name:"郭靖"
}
},{
name:"潘金莲",
hushband:{
name:"武大郎"
}
}
]);
一对多
多对多
索引
createIndex(keys, options)
语法
db.collection.createIndex(keys, options)
//实例 Key 值为你要创建的索引字段,1 为指定按升序创建索引,如果你想按降序来创建索引指定为 -1 即可。
db.col.createIndex({"title":1})
// 在后台创建索引
db.values.createIndex({open: 1, close: 1}, {background: true})
background | Boolean | 建索引过程会阻塞其它数据库操作,background可指定以后台方式创建索引,即增加 "background" 可选参数。 "background" 默认值为false。 |
---|---|---|
unique | Boolean | 建立的索引是否唯一。指定为true创建唯一索引。默认值为false. |
name | string | 索引的名称。如果未指定,MongoDB的通过连接索引的字段名和排序顺序生成一个索引名称。 |
dropDups | Boolean | **3.0+版本已废弃。**在建立唯一索引时是否删除重复记录,指定 true 创建唯一索引。默认值为 false. |
sparse | Boolean | 对文档中不存在的字段数据不启用索引;这个参数需要特别注意,如果设置为true的话,在索引字段中不会查询出不包含对应字段的文档.。默认值为 false. |
expireAfterSeconds | integer | 指定一个以秒为单位的数值,完成 TTL设定,设定集合的生存时间。 |
v | index version | 索引的版本号。默认的索引版本取决于mongod创建索引时运行的版本。 |
weights | document | 索引权重值,数值在 1 到 99,999 之间,表示该索引相对于其他索引字段的得分权重。 |
default_language | string | 对于文本索引,该参数决定了停用词及词干和词器的规则的列表。 默认为英语 |
language_override | string | 对于文本索引,该参数指定了包含在文档中的字段名,语言覆盖默认的language,默认值为 language. |
聚合
语法
db.COLLECTION_NAME.aggregate(AGGREGATE_OPERATION)
分片
- 复制所有的写入操作到主节点
- 延迟的敏感数据会在主节点查询
- 单个副本集限制在12个节点
- 当请求量巨大时会出现内存不足。
- 本地磁盘不足
- 垂直扩展价格昂贵
备份与恢复
备份
语法
-h ip:port ; -d 需要备份的实例; -o 备份的数据存放位置
mongodump -h dbhost -d dbname -o dbdirectory
备份所有MongoDB数据
mongodump --host runoob.com --port 27017
备份指定数据库的集合
mongodump --collection mycol --db test
恢复
语法
-h ip:port
-d 需要恢复的数据库
--drop恢复的时候先删除当前数据,然后恢复备份的数据。恢复后,备份后添加修改的数据都会被删除,慎用哦!
<path> 设置备份数据所在位置
--dir 指定备份的目录,不能同时指定<path>和dir选项
mongorestore -h <hostname><:port> -d dbname <path>
监控
mongostat
mongostat是mongodb自带的状态检测工具,在命令行下使用。它会间隔固定时间获取mongodb的当前运行状态,并输出。如果你发现数据库突然变慢或者有其他问题的话,你第一手的操作就考虑采用mongostat来查看mongo的状态。
$ mongostat
mongotop
mongotop也是mongodb下的一个内置工具,mongotop提供了一个方法,用来跟踪一个MongoDB的实例,查看哪些大量的时间花费在读取和写入数据。 mongotop提供每个集合的水平的统计数据。默认情况下,mongotop返回值的每一秒。
$ mongotop
$ mongotop 10 #10s刷新一次
- ns 包含数据库命名空间,后者结合了数据库名称和集合。
- db 包含数据库的名称。名为 . 的数据库针对全局锁定,而非特定数据库。
- total mongod花费的时间工作在这个命名空间提供总额。
- read 提供了大量的时间,这mongod花费在执行读操作,在此命名空间。
- write 提供这个命名空间进行写操作,这mongod花了大量的时间。
覆盖索引查询
- 所有的查询字段是索引的一部分
- 所有的查询返回字段在同一个索引中
创建联合索引,字段为 gender 和 user_name
db.users.ensureIndex({gender:1,user_name:1})
索引会覆盖以下查询
db.users.find({gender:"M"},{user_name:1,_id:0})
查询分析
查询分析常用函数有:explain() 和 hint()
explain()
- indexOnly: 字段为 true ,表示我们使用了索引。
- cursor:因为这个查询使用了索引,MongoDB 中索引存储在B树结构中,所以这是也使用了 BtreeCursor 类型的游标。如果没有使用索引,游标的类型是 BasicCursor。这个键还会给出你所使用的索引的名称,你通过这个名称可以查看当前数据库下的system.indexes集合(系统自动创建,由于存储索引信息)来得到索引的详细信息。
- n:当前查询返回的文档数量。
- nscanned/nscannedObjects:表明当前这次查询一共扫描了集合中多少个文档,我们的目的是,让这个数值和返回文档的数量越接近越好。
- millis:当前查询所需时间,毫秒数。
- indexBounds:当前查询具体使用的索引。
db.usersfind().explain()
hint()
虽然MongoDB查询优化器一般工作的很不错,但是也可以使用 hint 来强制 MongoDB 使用一个指定的索引。这种方法某些情形下会提升性能。 一个有索引的 collection 并且执行一个多字段的查询(一些字段已经索引了)。
db.users.find({gender:"M"},{user_name:1,_id:0}).hint({gender:1,user_name:1})
db.users.find({gender:"M"},{user_name:1,_id:0}).hint({gender:1,user_name:1}).explain()
高级索引
假设我们基于标签来检索用户,为此我们需要对集合中的数组 tags 建立索引。
在数组中创建索引,需要对数组中的每个字段依次建立索引。
建数组索引
db.users.ensureIndex({"tags":1})
检索集合的 tags 字段
db.users.find({tags:"cricket"})
索引子文档字段
假设我们需要通过city、state、pincode字段来检索文档,由于这些字段是子文档的字段,所以我们需要对子文档建立索引。
为子文档的三个字段创建索引,命令如下:
db.users.ensureIndex({"address.city":1,"address.state":1,"address.pincode":1})
旦创建索引,我们可以使用子文档的字段来检索数据:
db.users.find({"address.city":"Los Angeles"})
查询表达不一定遵循指定的索引的顺序,mongodb 会自动优化。
索引限制
额外开销
每个索引占据一定的存储空间,在进行插入,更新和删除操作时也需要对索引进行操作。所以,如果你很少对集合进行读取操作,建议不使用索引。
内存(RAM)使用
由于索引是存储在内存(RAM)中,你应该确保该索引的大小不超过内存的限制。
如果索引的大小大于内存的限制,MongoDB会删除一些索引,这将导致性能下降。
查询限制
索引不能被以下的查询使用:
- 正则表达式及非操作符,如 $nin, $not, 等。
- 算术运算符,如 $mod, 等。
- $where 子句
所以,检测你的语句是否使用索引是一个好的习惯,可以用explain来查看。
索引键限制
从2.6版本开始,如果现有的索引字段的值超过索引键的限制,MongoDB中不会创建索引。
插入文档超过索引键限制
如果文档的索引字段值超过了索引键的限制,MongoDB不会将任何文档转换成索引的集合。与mongorestore和mongoimport工具类似。
最大范围
- 集合中索引不能超过64个
- 索引名的长度不能超过128个字符
- 一个复合索引最多可以有31个字段
ObjectId
ObjectId 是一个12字节 BSON 类型数据,有以下格式:
- 前4个字节表示时间戳
- 接下来的3个字节是机器标识码
- 紧接的两个字节由进程id组成(PID)
- 最后三个字节是随机数。
MongoDB中存储的文档必须有一个"_id"键。这个键的值可以是任何类型的,默认是个ObjectId对象。
在一个集合里面,每个文档都有唯一的"_id"值,来确保集合里面每个文档都能被唯一标识。
MongoDB采用ObjectId,而不是其他比较常规的做法(比如自动增加的主键)的主要原因,因为在多个 服务器上同步自动增加主键值既费力还费时。
创建新的ObjectId
newObjId = ObjectId()
创建文档的时间戳
由于 ObjectId 中存储了 4 个字节的时间戳,所以你不需要为你的文档保存时间戳字段,你可以通过 getTimestamp 函数来获取文档的创建时间:
ObjectId("5349b4ddd2781d08c09890f4").getTimestamp()
ObjectId 转换为字符串
在某些情况下,您可能需要将ObjectId转换为字符串格式。
new ObjectId().str
MapReduce
Map-Reduce是一种计算模型,简单的说就是将大批量的工作(数据)分解(MAP)执行,然后再将结果合并成最终结果(REDUCE)。
MongoDB提供的Map-Reduce非常灵活,对于大规模数据分析也相当实用。
MapReduce 命令
基本说法
db.collection.mapReduce(
function() {emit(key,value);}, //map 函数
function(key,values) {return reduceFunction}, //reduce 函数
{
out: collection,
query: document,
sort: document,
limit: number
}
)
参数说明:
- map :映射函数 (生成键值对序列,作为 reduce 函数参数)。
- reduce 统计函数,reduce函数的任务就是将key-values变成key-value,也就是把values数组变成一个单一的值value。。
- out 统计结果存放集合 (不指定则使用临时集合,在客户端断开后自动删除)。
- query 一个筛选条件,只有满足条件的文档才会调用map函数。(query。limit,sort可以随意组合)
- sort 和limit结合的sort排序参数(也是在发往map函数前给文档排序),可以优化分组机制
- limit 发往map函数的文档数量的上限(要是没有limit,单独使用sort的用处不大)
命令:
db.posts.mapReduce(
function() { emit(this.user_name,1); },
function(key, values) {return Array.sum(values)},
{
query:{status:"active"},
out:"post_total"
}
)
输出结果:
{
"result" : "post_total",
"timeMillis" : 53,
"counts" : {
"input" : 1,
"emit" : 1,
"reduce" : 0,
"output" : 1
},
"ok" : 1
}
具体参数说明:
- result:储存结果的collection的名字,这是个临时集合,MapReduce的连接关闭后自动就被删除了。
- timeMillis:执行花费的时间,毫秒为单位
- input:满足条件被发送到map函数的文档个数
- emit:在map函数中emit被调用的次数,也就是所有集合中的数据总量
- ouput:结果集合中的文档个数**(count对调试非常有帮助)**
- ok:是否成功,成功为1
- err:如果失败,这里可以有失败原因,不过从经验上来看,原因比较模糊,作用不大
使用 find 操作符来查看 mapReduce 的查询
db.posts.mapReduce(
function() { emit(this.user_name,1); },
function(key, values) {return Array.sum(values)},
{
query:{status:"active"},
out:"post_total"
}
).find()
用类似的方式,MapReduce可以被用来构建大型复杂的聚合查询。
Map函数和Reduce函数可以使用 JavaScript 来实现,使得MapReduce的使用非常灵活和强大。
全文检索
启用全文检索
MongoDB 在 2.6 版本以后是默认开启全文检索的,如果你使用之前的版本,你需要使用以下代码来启用全文检索:
>db.adminCommand({setParameter:true,textSearchEnabled:true})
或者使用命令:
mongod --setParameter textSearchEnabled=true
创建全文索引
考虑以下 posts 集合的文档数据,包含了文章内容(post_text)及标签(tags):
{
"post_text": "enjoy the mongodb articles on Runoob",
"tags": [
"mongodb",
"runoob"
]
}
删除全文索引
删除已存在的全文索引,可以使用 find 命令查找索引名:
>db.posts.getIndexes()
正则表达式
正则表达式是使用单个字符串来描述、匹配一系列符合某个句法规则的字符串。
许多程序设计语言都支持利用正则表达式进行字符串操作。
MongoDB 使用 $regex 操作符来设置匹配字符串的正则表达式。
MongoDB使用PCRE (Perl Compatible Regular Expression) 作为正则表达式语言。
不同于全文检索,我们使用正则表达式不需要做任何配置。
使用正则表达式
以下命令使用正则表达式查找包含 runoob 字符串的文章:
>db.posts.find({post_text:{$regex:"runoob"}})
以上查询也可以写为:
>db.posts.find({post_text:/runoob/})
不区分大小写的正则表达式
如果检索需要不区分大小写,我们可以设置 $options 为 $i。
以下命令将查找不区分大小写的字符串 runoob:
>db.posts.find({post_text:{$regex:"runoob",$options:"$i"}})
数组元素使用正则表达式
我们还可以在数组字段中使用正则表达式来查找内容。 这在标签的实现上非常有用,如果你需要查找包含以 run 开头的标签数据(ru 或 run 或 runoob), 你可以使用以下代码:
>db.posts.find({tags:{$regex:"run"}})
优化正则表达式查询
- 如果你的文档中字段设置了索引,那么使用索引相比于正则表达式匹配查找所有的数据查询速度更快。
- 如果正则表达式是前缀表达式,所有匹配的数据将以指定的前缀字符串为开始。例如: 如果正则表达式为 ^tut ,查询语句将查找以 tut 为开头的字符串。
这里面使用正则表达式有两点需要注意:
正则表达式中使用变量。一定要使用eval将组合的字符串进行转换,不能直接将字符串拼接后传入给表达式。否则没有报错信息,只是结果为空!实例如下:
var name=eval("/" + 变量值key +"/i");
以下是模糊查询包含title关键词, 且不区分大小写:
title:eval("/"+title+"/i") // 等同于 title:{$regex:title,$Option:"$i"}
固定集合(Capped Collections)
MongoDB 固定集合(Capped Collections)是性能出色且有着固定大小的集合,对于大小固定,我们可以想象其就像一个环形队列,当集合空间用完后,再插入的元素就会覆盖最初始的头部的元素!
创建固定集合
我们通过createCollection来创建一个固定集合,且capped选项设置为true:
>db.createCollection("cappedLogCollection",{capped:true,size:10000})
还可以指定文档个数,加上max:1000属性:
>db.createCollection("cappedLogCollection",{capped:true,size:10000,max:1000})
判断集合是否为固定集合:
>db.cappedLogCollection.isCapped()
如果需要将已存在的集合转换为固定集合可以使用以下命令:
>db.runCommand({"convertToCapped":"posts",size:10000})
以上代码将我们已存在的 posts 集合转换为固定集合。
固定集合查询
固定集合文档按照插入顺序储存的,默认情况下查询就是按照插入顺序返回的,也可以使用$natural调整返回顺序。
>db.cappedLogCollection.find().sort({$natural:-1})
固定集合的功能特点
可以插入及更新,但更新不能超出collection的大小,否则更新失败,不允许删除,但是可以调用drop()删除集合中的所有行,但是drop后需要显式地重建集合。
在32位机子上一个cappped collection的最大值约为482.5M,64位上只受系统文件大小的限制。
固定集合属性及用法
属性
- 属性1:对固定集合进行插入速度极快
- 属性2:按照插入顺序的查询输出速度极快
- 属性3:能够在插入最新数据时,淘汰最早的数据
用法
- 用法1:储存日志信息
- 用法2:缓存一些少量的文档
自动增长
MongoDB 没有像 SQL 一样有自动增长的功能, MongoDB 的 _id 是系统自动生成的12字节唯一标识。
但在某些情况下,我们可能需要实现 ObjectId 自动增长功能。
由于 MongoDB 没有实现这个功能,我们可以通过编程的方式来实现,以下我们将在 counters 集合中实现_id字段自动增长。
使用 counters 集合
考虑以下 products 文档。我们希望 _id 字段实现 从 1,2,3,4 到 n 的自动增长功能。
{
"_id":1,
"product_name": "Apple iPhone",
"category": "mobiles"
}
为此,创建 counters 集合,序列字段值可以实现自动长:
>db.createCollection("counters")
现在我们向 counters 集合中插入以下文档,使用 productid 作为 key:
{
"_id":"productid",
"sequence_value": 0
}
sequence_value 字段是序列通过自动增长后的一个值。
使用以下命令插入 counters 集合的序列文档中:
>db.counters.insert({_id:"productid",sequence_value:0})