Mongo 命令集

开启 MongoDB 学习新篇章

Posted by lichao modified on August 4, 2021

示例场景

为便于代码演示,设想一个存储博客数据的应用场景,在集合中存储博客信息,其中包括博客标题、内容、标签、作者、点赞数、评论列表,文档结构如下:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
{
  "title": "my blog title",
  "author": "chao",
  "votes": 0,
  "content": "my blog content...",
  "comments": [{
                "author":"lc", 
                "content":"blog content one", 
              },
              {
                "author":"lc02",
                "content":"blog content one"
              }],
  "labels": ["ai", "it"]
}

操作数据库

查看当前指向的数据库

1
2
3
> db

test

选择数据库

1
> use test 

操作集合

集合(collection)是一组文档(document)的集合。集合类似于关系型数据库中表的概念,但相比表集合结构更为灵活,相同集合中的文档结构可以是不一样的。

一种特殊类型的集合,固定集合,需要事先创建好,而且它的大小是固定的(类似于循环队列)。当固定集合被占满时,如果在插入新文档,固定集合会自动将最老的文档从集合中删除。 固定集合的访问模式较为特殊,它的数据被顺序写入磁盘上的固定空间。固定集合适用于记录日志,固定集合不能被分片。固定集合的优点:

  1. 写入速度提升。固定集合中的数据被顺序写入磁盘上的固定空间,所以,不会因为其他集合的一些随机性的写操作而“中断”,其写入速度非常快(不建立索引,性能更好)。
  2. 固定集合会自动覆盖掉最老的文档,因此不需要再配置额外的工作来进行旧文档删除。设置Job进行旧文档的定时删除容易形成性能的压力毛刺。
创建集合

创建普通集合:

1
2
3
> db.createCollection("blog")

{ "ok" : 1 }

创建固定集合,固定集合必须在使用之前显示创建,且不能变更参数:

  • “max”指定文档数量限制
  • “size” 指定集合大小限制
1
2
3
> db.createCollection("blog", {"capped": true, "size": 100000, "max": 100})

{ "ok" : 1 }

上面的命令创建了一个名为 “blog” 大小为 100000 字节的固定集合,且只保存最新的 100个文档。固定集合的文档数量不能超过文档数量限制,固定集合的大小也不能超过大小限制。

查看集合

查看数据库中所有集合

1
2
3
> show collections

blog

1
2
3
> show tables

blog

查看数据库中所有集合的名称

1
2
3
> db.getCollectionNames() 

["blog"]

查看单个集合

1
2
3
> db.getCollection("blog") 

test.blog

查看集合详情:

1
2
3
> db.blog.stats() 

...

断集合是否为固定集合:

1
2
3
> db.blog.isCapped() 

...
集合重命名
1
2
3
> db.blog.renameCollection("blogs") 

{ "ok" : 1 }
删除集合
1
2
3
> db.blogs.drop()

true

插入数据

支持的数据类型:

  • null: 表示空值或者不存在的字段
  • 布尔型: 有两个值 true 或 false
  • 数值: 默认使用 64 位浮点型数值,整型数使用 NumberInt 类(表示 4 字节带符号整数)或 NumberLong 类(表示 8 字节带符号整数)表示
  • 字符串: UTF-8 字符串
  • 日期: 表示自新纪元以来的毫秒数

单个插入

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
> db.blog.insert({
  "title": "my blog title",
  "author": "chao",
  "votes": 0,
  "content": "my blog content...",
  "comments": [{
                "author":"lc", 
                "content":"blog content one", 
                "votes":0
              },
              {
                "author":"lc02",
                "content":"blog content one",
                "votes":0
              }],
  "labels": ["ai", "it"]
}) 

WriteResult({ "nInserted" : 1 })

批量插入

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
> db.blog.insertMany([{
  "title": "my blog title 01",
  "author": "chao",
  "votes": 0,
  "content": "my blog content...",
  "comments": [{
                "author":"lc", 
                "content":"blog content one", 
                "votes":0
              },
              {
                "author":"lc02",
                "content":"blog content one",
                "votes":0
              }],
  "labels": ["ai", "it"]
},
{
  "title": "my blog title 02",
  "author": "chao",
  "votes": 0,
  "content": "my blog content...",
  "comments": [{
                "author":"lc", 
                "content":"blog content one", 
                "votes":0
              },
              {
                "author":"lc02",
                "content":"blog content one",
                "votes":0
              }],
  "labels": ["ai", "it"]
}]) 


{
 "acknowledged" : true,
 "insertedIds" : [
  ObjectId("614457a4da8f2fe712df07f1"),
  ObjectId("614457a4da8f2fe712df07f2")
 ]
}

插入校验:

  • 如果没有”_id”字段,就自动增加一个
  • 所有文档都必须小于16MB

在一个集合里面,每个文档都有唯一的_id, 这个键的值可以是任意类型的,默认是个 ObjectId 对象。ObjectId 使用 12 字节的存储空间,是一个由 24 个十六进制数字组成的字符串: objectId结构

查找数据

查找数据,第一个参数是用于指定查询条件的文档。第二个参数是指定想要的键/想要剔除的键的文档,默认情况下”_id”这个键总是被返回。

1
2
3
4
> db.blog.find({}, {"title":1, "_id":0}) 

{ "title" : "my blog title" }
...

查找一个数据

1
2
> db.blog.findOne() 

范围 查询

$lt$lte$gt$gte 分别对应 <、 <=、 >、 >=

1
2
3
> db.blog.find({"votes":{"$gte":2, "$lte": 2}})

{ "_id" : ObjectId("6145a77ada8f2fe712df07f6"), "title" : "my blog title", "author" : "chao", "votes" : 2 ...

$ne 条件操作符表示 不相等。

1
> db.blog.find({"votes":{"$ne":2}})
or查询

$in 用于查询一个键的多个值,$nin 将返回与数组中所有条件都不匹配的文档。$or 接受一个包含所有可能条件的数组作为参数,可在多个键中查询任意的给定值。

1
2
3
4
5
6
7
> db.blog.find({"votes":{"$in":[2,3]}})

> db.blog.find({"votes":{"$nin":[2,3]}})

> db.blog.find({"$or":[{"title":"my blog title"}, {"votes":3}]})

> db.blog.find({"$or":[{"title":"my blog title"}, {"votes":{"$in":[2,3]}}]})

使用普通的AND型查询时,总是希望尽可能用最少的条件来限制结果的范围。 OR型查询正相反:第一个条件应该尽可能匹配更多的文档,这样才是最高效的。

$not

$mod 取模运算会将查询的值除以第一个给定值,若余数等于第二个给定值则匹配成功:

1
2
3
> db.blog.find({"votes":{"$mod":[2,1]}})

{ "_id" : ObjectId("6145b4a4f8cf6c4bea05c0fe"), "title" : "my blog title2", "votes" : 3...

$not 是元条件句,即可以用于任何其他条件之上,用来查找与特定模式不匹配的文档。执行逻辑 not 操作,在指定的表达式下查询不匹配表达式的文档。

1
2
3
> db.blog.find({"votes":{"$not":{"$mod":[2,1]}}})

{ "_id" : ObjectId("6145b4b8f8cf6c4bea05c106"), "title" : "my blog title2", "votes" : 2...
特定类型查询

null 不仅会匹配某个键的值为 null 的文档,还会匹配不包含这个键的文档

1
2
3
4
> db.blog.find({"author":null})

{ "_id" : ObjectId("6145b4a4f8cf6c4bea05c0fe"), "title" : "my blog title2", "author" : null }
{ "_id" : ObjectId("6145b4b8f8cf6c4bea05c106"), "title" : "my blog title2"}...

$exists 判断键值已存在。下面语句用于匹配键值为 null 的文档:

1
2
3
> db.blog.find({"author":{"$in":[null], "$exits": true}})

{ "_id" : ObjectId("6145b4a4f8cf6c4bea05c0fe"), "title" : "my blog title2", "author" : null }...
查询数组

查询数组元素和查询标量值是一样的。

1
2
3
> db.blog.find({"labels": "ios"})

{ "_id" : ObjectId("6145a77ada8f2fe712df07f6"), "title" : "my blog title", "labels" : [ "ios", "new label" ]}...

$all 通过多个元素来匹配数组,获取匹配一组元素的文档。数组元素顺序无关紧要。

1
2
3
> db.blog.find({"labels": {"$all":["new label", "ios"]}})

{ "_id" : ObjectId("6145a77ada8f2fe712df07f6"), "title" : "my blog title", "labels" : [ "ios", "new label" ]}...

key.index 可用于查询数组特定位置的元素。

1
2
3
> db.blog.find({"labels.2":  "new label"})

{ "_id" : ObjectId("6145a77ada8f2fe712df07f6"), "title" : "my blog title", "labels" : [ "ai","it", "new label" ]}...

size 用于查询特定长度的数组。

1
2
3
> db.blog.find({"labels":  {"$size":3}})

{ "_id" : ObjectId("6145a77ada8f2fe712df07f6"), "title" : "my blog title", "labels" : [ "ai","it", "new label" ]}...

$slice 可以返回某个键匹配的数组元素的一个子集。{"$slice": n} 返回前 n 个。{"$slice": -n} 返回后 n 个。{"$slice": [n,m]} 返回指定偏移量为 n,返回元素数量为 m 的集合中间位置的子集。默认使用 $slice 时会返回文档的所有键。

1
2
3
4
5
6
> db.blog.find({"title":"my blog title"},  {"comments":{"$slice": 2}})

> db.blog.find({"title":"my blog title"},  {"comments":{"$slice": -2}})

> db.blog.find({"title":"my blog title"},  {"comments":{"$slice": [2,2]}})

可以使用 $ 得到一个与查询条件相匹配的一个数组元素

1
2
> db.blog.find({"comments.author": "lc"},  {"comments.$": 1})

对于数组字段查询,如果数组中的某一个元素与查询条件的任意一条语句相匹配,那么这个文档也会被返回。

内嵌文档查询

查询整个内嵌文档,被查询的内嵌文档必须精准匹配(内嵌文档的键与查询文档的键的数量相同,而且与顺序相关)

1
2
3
4
5
6
7
> db.blog.find({ "comments": { 
    "author" : "lcss", 
    "content" : "blog content 22", 
    "votes" : 24
    }
  })

点表示法查询可只针对内嵌文档的特定键值进行查询,但是内嵌数组查询时,查询条件可能会对应到不同的内嵌文档:

1
2
3
4
5
> db.blog.find({ 
  "comments.author":"lcss" , 
  "comments.content":"blog content 22"
})

这是就要使用 $elemMatch 来在查询条件中部门指定匹配数组中的单个内嵌文档。

1
2
3
4
5
6
7
8
> db.blog.find({ "comments": { 
    "$elemMarch": {
      "author" : "lcss", 
      "votes" : 24
    }
  }
})

where 查询

$where 查询比常规慢很多,每个文档都要从 BSON 转换成 JavaScript 对象,然后通过 $where 表达式来运行。而且 $where 语句不能使用索引。

limit\skip\sort

limit 用于限定结果数量

1
> db.blog.find().limit(3)

skip 略过前 n 个匹配的文档

1
> db.blog.find().skip(3)

skip 过多的结果会导致性能问题,因为需要先找到被略过的数据,然后再抛弃这些数据

sort 接受一个对象作为参数,这个对象是一组键/值对,键对应文档的键名,值代表排序的方向。排序方向可以是1升序或-1降序。如果指定了多个键,则按照这些键被指定的顺序逐个排序。

1
> db.blog.find().sort({"title":1, "votes":-1})

sort 可以使用 {"$natural": 1} 进行自然排序。

1
> db.blog.find().sort({"$natural": 1})

自然排序(natural sort)返回结果集中文档的顺序就是文档在磁盘上的顺序。因为文档的位置经常变动,所以对于大多数普通集合来说,自然排序的意义不大。但是固定集合中的文档是按照文档被插入的顺序保存的,自然顺序就是文档的插入顺序,因此自然顺序得到的文档是从旧到新排列的。

高级查询选项

$maxscan 指定本次查询中扫描文档数量的上限。

1
> db.blog.find()._addSpecial("$maxscan", 20)

$min 指定查询的开始条件,查询文档必须与索引的键完全匹配。查询中会强制使用给定的索引。

$max 指定查询的结束条件,查询文档必须与索引的键完全匹配。查询中会强制使用给定的索引。

$showDiskLoc :true 在查询结果中添加一个 $diskLoc 字段,用于显示该条结果在磁盘上的位置。

1
> db.blog.find()._addSpecial("$showDiskLoc", true)

snapshot()对查询进行快照,查询在 _id 索引上遍历执行,可保证每个文档只被返回一次。快照会使查询变慢。

1
> db.blog.find().snapshot()

所有返回单批结果的查询都被有效的进行了快照。当游标正在等待获取下一批结果时,如果集合发生了变化,数据才可能出现不一致、

更新数据

update 方法通常有两个参数,一个是查询文档,用于定位需要更新的目标文档,另一个是修改器文档,用于说明要对找到的文档进行哪些修改。

通常文档只有一部分要更新,可以使用原子性的更新修改器,指定对文档中的某些字段进行更新。更新修改器是种特殊的键,用来指定复杂的更新操作,比如修改、增加或者删除键,还可能操作数组或者内嵌文档。

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
> db.blog.update({"title":"my blog title"}, {
  "title": "my blog new title",
  "author": "chao",
  "votes": 0,
  "comments": [{
                "author":"lc", 
                "content":"blog content one", 
                 "votes":0
              },
              {
                "author":"lc02",
                "content":"blog content one",
                 "votes": 0
              }],
  "labels": ["ai", "it"]
}) 

WriteResult({ "nMatched" : 1, "nUpserted" : 0, "nModified" : 1 })
更新修改器

通常文档只有一部分要更新,可以使用原子性的更新修改器,指定对文档中的某些字段进行更新。更新修改器是种特殊的键,用来指定复杂的更新操作,比如修改、增加或者删除键,还可能操作数组或者内嵌文档。 使用修改器时,_id的值不能改变,整个文档替换时,可以改变_id。其他的键,包括唯一索引的键,都是可以改变的。

$inc 修改器为指定 key 原子性的增加值,只能用于整型、长整形或双精度浮点型的值,用于其他类型会导致操作失败。

1
2
3
4
5
6
7
> db.blog.update({"title":"my blog new title"}, {
  "$inc":{
    "votes": 1
    }
  })

WriteResult({ "nMatched" : 1, "nUpserted" : 0, "nModified" : 1 })

$set 修改器用来指定一个字段的值,可用于更新或增加用户定义的键。甚至可以修改键的类型。

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
> db.blog.update({"title":"my blog new title"}, {
  "$set":{
    "author": "chao ge"
    }
  })

WriteResult({ "nMatched" : 1, "nUpserted" : 0, "nModified" : 1 })

> db.blog.update({"title": "my blog new title"}, {
  "$set":{
    "extr": "extr info"
    }
  })

WriteResult({ "nMatched" : 1, "nUpserted" : 0, "nModified" : 1 })

$unset 修改器用于删除键

1
2
3
4
5
6
7
> db.blog.update({"title":"my blog new title"}, {
  "$unset":{
    "extr": 1
    }
  })

WriteResult({ "nMatched" : 1, "nUpserted" : 0, "nModified" : 1 })

$push 修改器用于向已有的数组末尾加入一个元素,没有则创建一个新的数组。

1
2
3
4
5
6
7
8
> db.blog.update({"title":"my blog title"}, {
  "$push":{
    "labels": "cas"
    }
  })

WriteResult({ "nMatched" : 1, "nUpserted" : 0, "nModified" : 1 })

$push 修改器与$each子操作符结合,添加多个值。

1
2
3
4
5
6
7
8
9
> db.blog.update({"title":"my blog title"}, {
    "$push": {
      "labels":{
        "$each":[ "computer", "ios"]
      }
    }
  })

WriteResult({ "nMatched" : 1, "nUpserted" : 0, "nModified" : 1 })

$push 修改器与 $each、$slice 子操作符结合,可保证数组不会超出设定的最大长度。 $slice 的值必须是负数,保留最后的 n 个(最后添加的 n 个)。

1
2
3
4
5
6
7
8
9
10
> db.blog.update({"title": "my blog title"}, {
    "$push": {
      "labels":{
        "$each":[ "computer", "ios"],
        "$slice": -3
      }
    }
  })

WriteResult({ "nMatched" : 1, "nUpserted" : 0, "nModified" : 1 })

$push 修改器与 $each、$slice、$sort 子操作符结合,可保证数组不会超出设定的最大长度,保留排序之后的 n 个。

1
2
3
4
5
6
7
8
9
10
11
> db.blog.update({"title": "my blog title"}, {
    "$push": {
      "labels": {
        "$each": [ "computer", "ios"],
        "$slice": -3,
        "$sort": 1
      }
    }
  })

WriteResult({ "nMatched" : 1, "nUpserted" : 0, "nModified" : 1 })
1
2
3
4
5
6
7
8
9
10
11
> db.blog.update({"title": "my blog title"}, {
    "$push": {
      "comments": {
        "$each": [{ "author" : "lc66", "content" : "blog content 66", "votes" : 66 },{ "author" : "lc22", "content" : "blog content 22", "votes" : 22 }],
        "$slice": -3,
        "$sort": {"votes": -1}
      }
    }
  })

WriteResult({ "nMatched" : 1, "nUpserted" : 0, "nModified" : 1 })

$push 修改器与 $ne操作符结合,保证数组内元素不重复,但是每次只能修改一个文档。

1
2
3
4
5
6
7
8
> db.blog.update({ "labels":{"$ne":"new label"}}, {
  "$push":{
    "labels":"new label"
    }
  }
)

WriteResult({ "nMatched" : 1, "nUpserted" : 0, "nModified" : 1 })

$push 修改器与 $addToSet操作符结合,避免重复插入。

1
2
3
4
5
6
7
> db.blog.update({"title":"my blog title"}, {
  "$addToSet": {
    "labels": "new label"
    }
  })

WriteResult({ "nMatched" : 1, "nUpserted" : 0, "nModified" : 1 })

$push 修改器与 $addToSet、$each操作符结合,插入多个值,同时避免重复插入。

1
2
3
4
5
6
7
> db.blog.update({"title":"my blog title"}, {
  "$addToSet": {
    "labels": {"$each":["label1","label2"]}
  }
})

WriteResult({ "nMatched" : 1, "nUpserted" : 0, "nModified" : 1 })

通过位置或定位操作符($)操作数组中的值。数组下表以 0 开始,可以将下标直接作为键来选择元素,例如增加第一个评论的

1
2
3
4
5
6
7
8
> db.blog.update({"title":"my blog title"}, {
  "$inc": {
    "comments.0.votes": 1
    }
  }) 

WriteResult({ "nMatched" : 1, "nUpserted" : 0, "nModified" : 1 })

可使用定位操作符”$”来定位查询文档已经匹配的数据元素,并进行更新。定位符只能更新第一个匹配的元素。

1
2
3
4
5
> db.blog.update({"comments.author":"lc66"}, {
  "$set": {
    "comments.$.author": "lc"
    }
  }) 

若把数组看成队列或者栈,可以用 $pop修改器从数组任何一端删除元素,{ "$pop":{"key": 1}从数组末尾删除一个元素,{ "$pop":{"key":-1}则从头部删除元素。

1
2
3
4
5
6
7
> db.blog.update({"title":"my blog title"}, {"$pop": {"labels": 1}}) 

WriteResult({ "nMatched" : 1, "nUpserted" : 0, "nModified" : 1 })

> db.blog.update({"title":"my blog title"}, {"$pop": {"labels": -1}}) 

WriteResult({ "nMatched" : 1, "nUpserted" : 0, "nModified" : 1 })

可以用 $pull修改器删除匹配的所有元素。

1
2
3
> db.blog.update({"title":"my blog title"}, {"$pull": {"labels": "laundry"}}) 

WriteResult({ "nMatched" : 1, "nUpserted" : 0, "nModified" : 1 })

当数组修改器改变文档的大小时,可能会造成插入性能下降: 将文档插入到 MongoDB 时,依次插入的文档在磁盘上的位置是相邻的,如果中间的一个文档变大了,原先的位置就放不下了,这个文档就会被移动到集合中的另一个位置。 当MongoDB不得不移动一个文档时,它会修改集合的填充因子,填充因子是MongoDB为每个新文档预留的增长空间。可以查看db.coll.stats()查看填充因子。 如果业务在进行插入和删除时会进行大量的移动或经常打乱数据,可以使用 usePowerOfSizes 选项以提高磁盘复用率,这个集合之后进行的所有空间分配,得到的块大小都是 2 的幂。由于这个选项会导致初始空间分配不再那么高效,所以应该只有需要经常打乱数据的集合上使用。在一个只进行插入和原地更新的集合上使用这个选项,会导致写入速度变慢。

upsert 在没有找到符合查询条件的文档时,会以这个条件和更新文档为基础创建一个新的文档,如果找到了匹配的文档,则正常更新。而且 upsert 是原子性的、能够避免竞态(重复插入)问题。udpate 的第三个参数表示这是一个 upsert:

1
2
3
4
5
> db.blog.update({"title": "my blog title2"}, {
  "$inc": {
    "votes": 1
    }
  }, true) 

```javascript
> db.blog.update({"title":"my blog title"}, {
  "$setOnInsert": {
    "createdAt": new Date()
    }}, true) 

WriteResult({ "nMatched" : 1, "nUpserted" : 0, "nModified" : 0 })

更新多个文档。默认情况下,更新只能对符合条件的第一个文档执行操作。如果需要更新所有匹配的文档,可以将 update 的第四个参数设置为 true。下面是给指定生日的所有用户添加了 gift 键。

1
2
3
> db.blog.update({}, {"$set": {"createdAt": new Date()}}, false, true) 

WriteResult({ "nMatched" : 7, "nUpserted" : 0, "nModified" : 7 })

删除数据

数据删除是永久性的,不能撤销,也不能恢复。

1
> db.blog.remove({"title":"my blog title"})