MongoDB 学习笔记
基础知识
- 结构
- 文档:相当于关系数据表中的一行
- 命名:不可包含
\0
,代表键的结尾;.
与$
具有特殊意义,仅在特定环境使用。
- 命名:不可包含
- 集合:相当于一张表(动态模式)
- 命名:不能为空字符串;不能包含
\0
;不能以system.
开头,为系统集合保留前缀;$
符号为保留符号,除非要访问系统创建的集合。 - 子集合:
db.parent.children
,其中parent
集合甚至不需要存在
- 命名:不能为空字符串;不能包含
- 数据库
- 命名:不能是空字符串;不能包含
/
、\
、.
、"
、*
、<
、>
、:
、|
、?
、$
、\0
,基本上只能使用 ASCII 码;数据库名区分大小写,建议全为小写;数据库名最多为64字节;
- 命名:不能是空字符串;不能包含
- 文档:相当于关系数据表中的一行
安装
- 下载链接:
https://www.mongodb.com/try/download/community
,选择 Zip 版本 - 创建文件存储路径:
..\data\db
,设置文件存储路径且启动 mongo 命令mongod --dbpath x:\..\data\db
- 再打开一个 cmd 终端,启动 Shell 脚本命令
mongo
Shell 使用
-
中断多行命令输入,连续三次按下回车键
-
取消默认连接数据库启动 Shell 脚本命令
mongo --nodb
> mongo --nodb > conn = new Mongo("127.0.0.1:27017") > db = conn.getDB("myDB")
-
查看帮助
- 数据库级别帮助:
db.help()
- 集合级别帮助:
db.集合名.help()
- 具体方法实现:
db.集合名.方法名
,不带括号
- 数据库级别帮助:
-
使用 Shell 脚本
- 依次运行脚本:
mongo xxxx1.js xxxx2.js xxxx3.js
- 指定数据库运行脚本:
mongo --quiet host:post/dbname xxxxx.js
(--quiet
不打印提示) - 在 Shell 内启动脚本:
load("xxxx.js")
-
创建
.mongorc.js
,如果默写脚本频繁被加载,可以将它们添加到文件中,会在启动 Shell 时自动运行-
启动时指定
--norc
参数可以禁止加载.mongorc.js
-
可以创建需要的全局变量,或为太长名字创建一个简短的别名,也可以重写内置函数。最常见的用途是移除危险操作
var no = function() { print("Not on my watch."); } // 禁止删除数据库 db.dropDatabase = DB.prototype.dropDatabase = no; // 禁止删除集合 DBCollection.prototype.drop = no; // 禁止删除索引 DBCollection.prototype.dropIndex = no;
-
- 依次运行脚本:
数据库操作
- 查看当前指向数据库
db
- 查看所有数据库
show dbs
- 查看所有集合
show collections
- 切换指向数据库
use db名称
支持基本数据类型
null
:用于表示空值或者不存在的字段- 布尔值
- 数值:shell 默认使用64位浮点型数值。
- 整数型:可使用
NumberInt
或NumberLong
,如{"x": NumberInt("3")}
- 整数型:可使用
- 字符串
- 日期:自新纪元以来经过的毫秒数,不存储时区,如
{"x": new Date()}
- 正则表达式:使用正则表达式作为限定条件,如
{"x": /foobar/i}
- 数组:数组可包含不用数据类型的元素
- 内嵌文档
- 对象 Id:
{"x": ObjectId()}
- 二进制数据
- 代码:查询和文档中可以包括任意 JavaScript 代码
CURL
创建
-
命令:
db.集合名.insert()
插入,传递数组对象则批量插入 -
批量插入中当某一文档插入失败,则该文档之前的文档插入成功,该文档及以后的文档插入失败。如需忽略错误继续插入,可使用
continueOnError
选项,Shell 不支持该功能,所有驱动程序支持。> post = { ... title: "This is test post", ... content: "hellow,every body", ... date: new Date() ... } > db.blog.insert(post)
读取
基础命令
-
命令:
db.集合名.find()
或db.集合名.findOne()
,使用find
时最多显示20个文档。 -
第一个参数为查询限定条件;第二个参数为返回字段,为1则返回,为0则排除;
> searchPost = { ... content = "test" ... } > db.blog.find(searchPost, {"x": 1, "y": 0})
-
读取并更新/删除记录,使用
findAndModify
> db.runCommand({ "findAndModify": "blog", "query": {}, "sort": {"_id":-1}, "update": {"$set": {"newStatus":"test"}}, // update/remove 至少有且仅有一个 "remove": true, "new": true, // 返回更新前/后的文档,默认为更新前 "fields": [] // 文档中需要返回的字段 }) { // 通过.value 取值 "lastErrorObject" : { "n" : 1, "updatedExisting" : true }, "value" : { "_id" : ObjectId("6077acf95adb3d00d951a22b"), }, "ok" : 1 }
查询限定条件
-
$lt
、$lte
、$gt
、$gte
、$ne
对应<、≤、>、≥、≠> db.user.find({age: {$gte: 21, $lte: 23}}) > start = new Date("01/01/2019") // 文档中日期精确到毫秒,所以范围查询很有必要 > db.user.find({time: {$gt: start}}) > db.user.find({name: {$ne: "zzz"}})
-
$in
属于、$nin
全部不属于> db.user.find({age: {$in: [18, 22, 23]}}) > db.user.find({age: {$nin: [18, 22, 23]}})
-
$or
或、$and
且,查询优化器不会对 and 进行优化> db.user.find({$or: [{age: {$in: [21, 22]}}, {name: "zym"}]}) // 第一个条件尽可能多的匹配文档 > db.user.find({$and: [{arr: {$lt: 1}}, {arr: 4}]}) // 可匹配到 arr 为数组 [0,4] > db.user.find({arr: {$lt: 1, $in: [4]}}) // 同样匹配 [0,4] 数组,但效率更高
-
$not
非 -
$mod
取模,查询结果除以第一个给定值,若余数等于第二个给定值则匹配成功> db.user.find({age: {$not: {$mod: [3, 0]}}})
-
$exists
字段存在检验,用于匹配 null 值> db.user.find({arr: null}) // 不仅会匹配字段为 null 的文档,还会匹配不包含该字段的文档 > db.user.find({arr: {$in: [null], $exists: true}}) // 匹配字段存在且为 null 的文档
-
/……/
正则表达式匹配> db.user.find({name: /^z.*/}) > db.user.insert({name: /^z.*/}) // 文档内可存储正则表达式,同时可匹配自身
数组限定条件
-
通用匹配:
db.user.find({arr: 4})
数组内包含 4 则匹配成功 -
全部包含匹配:
db.user.find({arr: {$all: [0, 4]}})
数组内必须包含 0,4 两个元素,但顺序无关 -
精确匹配:
> db.user.find({arr: [0, 4]})
必须为 [0, 4] 数组 -
下标元素匹配:
> db.user.find({"arr.1": 4})
下标 1 的位置为 4;Key 必须带双引号 -
数组大小匹配:
> db.user.find({arr: {$size: 3}})
返回数组大小为 3 的文档,不能与其他查询限定条件联合使用。为满足需求,通常在文档内增加 size 字段 -
数组截取:
> db.user.find({}, {arr: {$slice: 2}})
返回开始两个元素、当为负数时返回结尾两个元素 -
数组偏移与截取:
> db.user.find({}, {arr: {$slice: [2, 3]}})
偏移 2 位,截取 3 位数组 -
数组下标定位:
> db.user.find({'arr': 2}, {'arr.$': 1})
使用$
定位符查询时,查询条件必须包含定位字段 -
数组且查询:
> db.user.find({arr: {$elemMatch: {$gt: 4, $lt: 8}}})
查询包含大于 4 且小于 8 元素的数组;查询若为> db.user.find({arr: {$gt: 4, $lt: 8}})
则查询包含元素大于 4 或小于 8 的数组数组内嵌文档且查询:
db.user.find({arr: {$elemMatch: {x: ?, y: {$lt: ?}}}})
Where查询限定
在$Where
查询中,可以执行任意的 JS 语句
游标
-
将查询结果放入局部变量中,即可获得游标(通过
var
声明的为局部变量);可以通过hasNext()
方法查看时候还有结果;通过next()
获取结果;游标实现了forEach()
迭代方法,可直接调用方法进行便利。 -
限制:
.limit(n)
-
跳过:
.skip(n)
;当抛弃少量数据时skip时明智的选择,但是当跳过大量数据时skip将会变得很慢,会先将内容读取后在抛弃。- 分页:不直接使用skip进行分页;当有排序字段时,读取完最后一条文档后,用排序字段进行比较查询下次结果。
- 随机抽取:添加额外随机键,查询时取随机数进行大于或小于比较,判断取第一条。
-
排序:
.sort({key: 1/ -1})
,1为升序,-1为降序排序优先级:
最小值-null-数字-字符串-对象/文档-数组-二进制数据-对象ID-布尔值-日期型-时间戳-正则表达式-最大值
更新
-
命令:
db.集合名.update()
;第一个参数为限定条件;第二个参数为新的文档;第三个参数为是否使用upsert
;第四个参数为是否更新多条匹配数据,默认仅更新第一条匹配数据。 -
upsert
命令当查询条件文档存在则更新,若文档不存在则创建后更新> post.comments = [] > db.blog.update({title: "This is test post"}, post, {upsert: true}, {multi: false})
-
$setOnInsert
当文档创建时赋值,之后的更新中该字段不会改变(文档存在字段不存在,则字段也不会创建),如db.test.update({}, {"$setOnInsert": {"createdAt": new Date()}}, true)
save()
为 Shell 提供的函数,效果同upsert()
-
查看更新多少行,可以使用
db.runCommand({getLastError: 1})
,返回最后一次操作信息
修改器
- 创建或修改:
db.集合名.update({}, {"$set": {"x.y": ?}})
,不存在字段则创建,存在则修改。可修改内嵌文档如x.y
- 自增字段:
db.集合名.update({}, {"$inc": {"x": n}})
,原子性修改字段 x 自增 n - 数组操作
- 插入元素:
db.集合名.update({}, {"$push": {"x": ?}})
,不存在则创建且插入,存在则像数组末尾插入元素 - 删除元素
- 删除指定位置元素:
{"$pop": {"key": 1}}
,正数从尾部删除、负数从头部删除 - 删除指定值元素:
{"$pull": {?, ?}}
,删除匹配值的所以元素
- 删除指定位置元素:
- 定位操作符:
db.集合名.update({"x": ?}, {"$inc": {"x.$.y": ?}})
,此时$
代表查询到的第一条记录下标,查询条件必须包含定位字段 - 子操作符
- 遍历:
db.集合名.update({}, {"$push": {"x": { "$each": [?, ?, ?]}}})
,遍历数组内容插入数组内 - 限制个数:
db.集合名.update({}, {"$push": {"x": { "$each": [?, ?, ?], "$slice": -10}}})
,slice 值必须为负整数,限制文档字段的数组元素个数 - 排序:
db.集合名.update({}, {"$push": {"x": { "$each": [?, ?, ?], "$sort": {"y": -1}}}}})
,按照 y 字段排序,并插入
- 遍历:
- 插入元素:
- 不存在查询:
db.集合名.update({"x": {"$ne": ?}}, {})
,查询 x 不存在 ? 的文档,并修改 - 不存在插入(避免重复值):
db.集合名.update({},{"$addToSet": {"$each": []}})
,遍历不存在则插入
修改器效率
- 当改变文档大小时,原位置无法存储该文档,则会释放原内存,并分配新空间。
- 当不得不移动文档时,会修改填充因子扩大每个新文档的预留区域,当之后插入不在发生文档移动时,则填充因子会逐渐变小。
- 当频繁移动文档导致效率出现瓶颈,则考虑将内嵌文档单独放入一个集合中。
删除
-
命令:
db.集合名.remove()
,接受一个作为限定条件的文档作为参数,若没有参数,将删除集合内所有文档。 -
若需要清空或删除文档时,
db.集合名.drop
效率最高> db.blog.remove({title: "This is test post"})
索引
-
查询效率分析:
.explain("executionStats")
-
强制使用索引:
.hint({"x": 1, "y": 1})
;强制全表扫描{"$natural": 1}
-
创建索引:
.ensureIndex({"x": 1, "y": 1}, {"name": ?})
;1为升序,在基于多查询条件排序是,复合索引顺序对效率产生影响。当复合索引中每个键上的索引顺序都相反,如x
与{"x": -1, "y": 1}
是等价的。-
db.users.find({"x": ?}).sort({"y": 1})
-
db.users.find({"x": {"$gte": 21, "lte": 30}})
-
db.users.find({"x": {"$gte": 21, "lte": 30}}).sort({"y": 1})
该find查询结果集中y是无序的,mongo需要在内存中进行排序;若查询结果集庞大,则将x,y索引调换,.ensureIndex({"y": 1, "x": 1})
,将排序字段放在前面可增加效率。实验
-
查询数据集庞大时使用查询字段索引,效率高
db.users.find({"x": {"$gte": 21, "lte": 30}}).sort({"y": 1}).hint({"x": 1, "y": 1})
2766msdb.users.find({"x": {"$gte": 21, "lte": 30}}).sort({"y": 1}).hint({"y": 1, "x": 1})
14820ms -
查询数据集较小时使用排序字段索引,效率高
db.users.find({"x": {"$gte": 21, "lte": 30}}).sort({"y": 1}).limit(1000).hint({"x": 1, "y": 1})
2031msdb.users.find({"x": {"$gte": 21, "lte": 30}}).sort({"y": 1}).limit(1000).hint({"y": 1, "x": 1})
181ms
-
-
-
唯一索引去除重复值:
.ensureIndex({}, {"unique": true, "dropDups": true})
创建唯一索引,且当遇到重复值时仅保留第一条数据 -
稀疏索引:
.ensureIndex({}, {"unique": true, "sparse": true})
,若sparse不与unique联合使用则可创建不唯一的稀疏索引;稀疏索引只是不需要将每个文档都作为索引条目。 -
删除索引:
.dropIndex(?)