MongoDB 4.2——入门指南

MongoDB 4.2入门指南

1、文档

文档是 MongoDB 的核心概念:它是一组有序键值的集合。文档的表示形式因编程语言而异,但大多数语言具有自然匹配的数据结构,比如映射、哈希表或字典。例如,在 JavaScript 中,文档表示为对象:

js 复制代码
{"greeting" : "Hello, world!"}

这个简单的文档只包含一个键,即 "greeting",对应的值为 "Hello, world!"。大多数文档会比这个例子更复杂,并且通常会包含多个键--值对:

js 复制代码
{"greeting" : "Hello, world!", "views" : 3}

如上所示,文档中的值不仅仅是"二进制大对象"​,它们可以是几种不同的数据类型之一​。在本例中,"greeting"的值是一个字符串,而 "views" 的值是一个整数。

文档中的键是字符串类型。除了少数例外的情况,可以使用任意 UTF-8 字符作为键。

  • 键中不能含有 \0(空字符)。这个字符用于表示一个键的结束。
  • .$ 是特殊字符,只能在某些特定情况下使用,后文会详细说明。通常来说,可以认为这两个字符属于保留字符,如果使用不当,那么驱动程序将无法正常工作。

MongoDB 会区分类型和大小写。例如,下面这两个文档是不同的:

js 复制代码
{"count" : 5}
{"count" : "5"}

下面这两个文档也不同:

js 复制代码
{"count" : 5}
{"Count" : 5}

需要注意,MongoDB 中的文档不能包含重复的键。例如,下面这个文档是不合法的。

js 复制代码
{"greeting" : "Hello, world!", "greeting" : "Hello, MongoDB!"}

2、集合

集合就是一组文档。如果将文档比作关系数据库中的行,那么一个集合就相当于一张表。

2.1、动态模式

集合具有动态模式的特性。这意味着一个集合中的文档可以具有任意数量的不同"形状"​。例如,以下两个文档可以存储在同一个集合中:

js 复制代码
{"greeting" : "Hello, world!", "views": 3}
{"signoff": "Good night, and good luck"}

需要注意的是,以上文档中的键、键的数量以及值的类型都是不同的。由于任何文档都可以放入集合中,因此经常会出现这样的问题:​"为什么还需要多个集合呢?​"既然不同类型的文档不需要区分模式,为什么还要使用多个集合呢?有以下几点原因。

  • 对于开发人员和管理员来说,将不同类型的文档保存在同一个集合中可能是一个噩梦。开发人员需要确保每个查询只返回特定模式的文档,或者确保执行查询的应用程序代码可以处理不同类型的文档。如果在查询博客文章时还需要剔除包含作者数据的文档,那么这会非常麻烦。
  • 获取集合列表比提取集合中的文档类型列表要快得多。如果在每个文档中都有一个 "type" 字段来指明这个文档是"skim""whole"还是"chunky monkey",那么在单个集合中查找这 3 个值要比查询 3 个相应的集合慢得多。
  • 将相同类型的文档放入同一个集合中可以实现数据的局部性。相对于从既包含博客文章又包含作者数据的集合中进行查询,从一个只包含博客文章的集合中获取几篇文章可能会需要更少的磁盘查找次数。
  • 在创建索引(尤其是在创建唯一索引)时,我们会采用一些文档结构。这些索引是按照每个集合来定义的。通过只将单一类型的文档放入集合中,可以更高效地对集合进行索引。

创建模式并且将相关类型的文档放在一起是非常合理的。虽然默认情况下为应用程序定义模式并非必需,但这是一种很好的实践,可以通过使用 MongoDB 的文档验证功能和可用于多种编程语言的对象--文档映射(object-document mapping)库来实现。

2.2、命名

集合由其名称进行标识。集合名称可以是任意 UTF-8 字符串,但有以下限制。

  • 集合名称不能是空字符串("")。
  • 集合名称不能含有 \0(空字符),因为这个字符用于表示一个集合名称的结束。
  • 集合名称不能以 system. 开头,该前缀是为内部集合保留的。例如,system.users 集合中保存着数据库的用户,system.namespaces 集合中保存着有关数据库所有集合的信息。
  • 用户创建的集合名称中不应包含保留字符 。许多驱动程序确实支持在集合名称中使用 ,这是因为某些由系统生成的集合会包含它,但除非你要访问的是这些集合之一,否则不应在名称中使用 $ 字符。

子集合

使用 . 字符分隔不同命名空间的子集合是一种组织集合的惯例。例如,有一个具有博客功能的应用程序,可能包含名为 blog.posts 和名为 blog.authors 的集合。这只是一种组织管理的方式,blog 集合(它甚至不必存在)与其"子集合"之间没有任何关系。

尽管子集合没有任何特殊属性,但它们很有用,许多MongoDB 工具整合了子集合。

  • GridFS 是一种用于存储大型文件的协议,它使用子集合将文件元数据与内容块分开存储。
  • 大多数驱动程序为访问指定集合的子集合提供了一些语法糖。例如,在数据库 shell 中,使用 db.blog 可以访问 blog 集合,使用 db.blog.posts 可以访问 blog.posts 集合。

在 MongoDB 中,使用子集合来组织数据在很多场景中是一个好方法。

3、数据库

MongoDB 使用集合对文档进行分组,使用数据库对集合进行分组。一个 MongoDB 实例可以承载多个数据库,每个数据库有零个或多个集合。一个值得推荐的做法是将单个应用程序的所有数据都存储在同一个数据库中。在同一个 MongoDB 服务器上存储多个应用程序或用户的数据时,使用单独的数据库会非常有用。

与集合相同,数据库也是按照名称进行标识的。数据库名称可以是任意 UTF-8 字符串,但有以下限制。

  • 数据库名称不能是空字符串("")。
  • 数据库名称不能包含 /、\、.、"、*、<、>、:、|、?、$、单一的空格以及 \0(空字符),基本上只能使用 ASCII 字母和数字。
  • 能使用 ASCII 字母和数字。
  • 数据库名称区分大小写。
  • 数据库名称的长度限制为 64 字节。

在 MongoDB 使用 WiredTiger 存储引擎之前,数据库名称会对应文件系统中的文件名。尽管现在已经不这样处理了,但之前的许多限制遗留了下来。

此外,还有一些数据库名称是保留的。这些数据库可以被访问,但它们具有特殊的语义。具体如下。

  • admin:admin 数据库会在身份验证和授权时被使用。此外,某些管理操作需要访问此数据库。
  • local:特定于单个服务器的数据会存储在此数据库中。在副本集中,local 用于存储复制过程中所使用的数据,而local 数据库本身不会被复制。
  • config:MongoDB 的分片集群会使用config 数据库存储关于每个分片的信息。

通过将数据库名称与该库中的集合名称连接起来,可以获得一个完全限定的集合名称,称为命名空间。如果你要使用 cms 数据库中的 blog.posts 集合,则该集合的命名空间为 cms.blog.posts。命名空间的长度限制为120 字节,而实际使用时应该小于 100 字节

4、启动MongoDB

要启动 MongoDB 服务器端,请在 Unix 命令行环境中运行 mongod 可执行文件:

bash 复制代码
$ mongod

如果使用的是 Windows 系统,执行这个命令。

bash 复制代码
> mongod.exe

如果没有指定参数,则 mongod 会使用默认的数据目录/data/db/(在 Windows 系统中为当前卷的\data\db\)​。如果数据目录不存在或不可写,那么服务器端将无法启动。因此在启动 MongoDB 之前,创建数据目录(如 mkdir -p /data/db/)并确保对该目录有写权限非常重要。

启动时,服务器端会打印版本和系统信息,然后开始等待连接。默认情况下,MongoDB 会监听 27017 端口上的套接字连接。如果端口不可用,那么服务器将无法启动------最常见的原因是有另一个 MongoDB 实例正在运行。

可以在启动 mongod 服务器端的命令行环境中键入Ctrl-C 来安全地停止 mongod 实例。

5、MongoDB shell介绍

MongoDB 自带 JavaScript shell,允许使用命令行与MongoDB 实例进行交互。shell 在很多场景中非常有用,包括执行管理功能、检查正在运行的实例或仅仅是探索 MongoDB。

5.1、运行shell

要启动 shell,请运行 mongo 可执行文件:

bash 复制代码
$ mongo
MongoDB shell version: 4.2.0
connecting to: test
>

shell 在启动时会自动尝试连接到本地机器上运行的MongoDB 服务器端,因此在启动 shell 之前,请先确保mongod 已启动。

shell 是一个功能齐全的 JavaScript 解释器,能够运行任意的 JavaScript 程序。为了说明这一点,下面来进行一些基本的数学运算:

bash 复制代码
> x = 200;
200
> x / 5;
40

还可以利用所有的 JavaScript 标准库:

bash 复制代码
> Math.sin(Math.PI / 2);
1
> new Date("2019/1/1");
ISODate("2019-01-01T05:00:00Z")
> "Hello, World!".replace("World", "MongoDB");
Hello, MongoDB!

甚至可以定义和调用 JavaScript 函数:

bash 复制代码
> function factorial (n) {
... if (n <= 1) return 1;
... return n * factorial(n - 1);
... }
> factorial(5);
120

需要注意,你可以创建多行命令。当按下回车键时,shell 将检测 JavaScript 语句是否完整。如果语句不完整,那么 shell 将允许你在下一行继续进行编写。连续 3次按下回车键将取消未输入完成的命令并返回到 > 提示符。

5.2、MongoDB客户端

尽管能执行任意 JavaScript 程序的能力非常有用,但shell 真正的功能在于它是一个独立的 MongoDB 客户端。启动时,shell 会连接到 MongoDB 服务器端的 test数据库,并将此数据库连接赋值给全局变量 db。此变量是通过 shell 访问 MongoDB 服务器端的主要入口点。

要查看 db 当前指向哪个数据库,请键入 db 并按回车键:

js 复制代码
> db
test

为了方便习惯使用 SQL shell 的用户,shell 包含了一些不是有效 JavaScript 的扩展语法。这些扩展不提供任何额外的功能,但它们是很好的语法糖。例如,最重要的操作之一是选择要使用的数据库:

js 复制代码
> use video
switched to db video

现在,如果查看 db 变量,可以看到它指向了 video 数据库:

js 复制代码
> db
video

因为这是一个 JavaScript shell,所以输入一个变量名会将此变量作为表达式计算(在本例中是数据库名称)并打印出来。

可以通过 db 变量来访问集合,如下所示:

js 复制代码
> db.movies

这会返回当前数据库中的 movies 集合。既然可以通过shell 访问集合,就意味着可以在 shell 中执行大部分数据库操作。

5.3、shell中的基本操作

可以使用创建、读取、更新以及删除(CRUD)这 4 种基本操作在 shell 中操作和查看数据。

5.3.1、创建

insertOne 函数可以将一个文档添加到集合中。假如我们想存储一部电影。首先,创建名为 movie 的局部变量,它是用来表示这个文档的 JavaScript 对象。它会有几个键:"title"、"director" 和"year"(发行年份)​。

js 复制代码
> movie = {"title" : "Star Wars: Episode IV - A New Hope",
... "director" : "George Lucas",
... "year" : 1977}
{
        "title" : "Star Wars: Episode IV - A New Hope",
        "director" : "George Lucas",
        "year" : 1977
}

这个对象是一个有效的 MongoDB 文档,因此可以用 insertOne 方法将其保存到 movies 集合中:

js 复制代码
> db.movies.insertOne(movie)
{
        "acknowledged" : true,
        "insertedId" : ObjectId("5721794b349c32b32a012b11")
}

这部电影已经被存入数据库中。可以通过调用集合的find 方法对其进行查看:

js 复制代码
> db.movies.find().pretty()
{
        "_id" : ObjectId("5721794b349c32b32a012b11"),
        "title" : "Star Wars: Episode IV - A New Hope",
        "director" : "George Lucas",
        "year" : 1977
}

可以看到输入的键--值对都完整地保存了下来,并额外添加了一个 "_id" 键。本章的最后会解释 "_id" 字段突然出现的原因。

5.3.2、读取

find 和 findOne 方法可用于查询集合中的文档。如果只想查看一个文档,可以使用 findOne:

js 复制代码
> db.movies.findOne()
{
        "_id" : ObjectId("5721794b349c32b32a012b11"),
        "title" : "Star Wars: Episode IV - A New Hope",
        "director" : "George Lucas",
        "year" : 1977
}

find 和 findOne 也可以接受一个查询文档作为限定条件。这样就可以限制查询匹配到的文档了。使用find 时,shell 将自动显示最多 20 个匹配的文档,但也可以获取更多文档

5.3.3、更新

如果想修改文档,可以使用 updateOne。updateOne 会接受(至少)两个参数:第一个用于查找要更新文档的限定条件,第二个用于描述要进行更新的文档。假设我们决定为先前创建的电影启用评论功能,那么就需要在文档中添加一个新的键,用于保存评论的数组。

要执行更新,需要使用更新运算符 set:

js 复制代码
> db.movies.updateOne({title : "Star Wars: Episode IV - A New Hope"},
... {$set : {reviews: []}})
WriteResult({"nMatched": 1, "nUpserted": 0, "nModified": 1})

现在文档有了一个 "reviews" 键。如果再次调用find,可以看到这个新生成的键:

js 复制代码
> db.movies.find().pretty()
{
        "_id" : ObjectId("5721794b349c32b32a012b11"),
        "title" : "Star Wars: Episode IV - A New Hope",
        "director" : "George Lucas",
        "year" : 1977,
        "reviews" : [ ]
}
5.3.4、删除

deleteOne 和 deletemany 方法会从数据库中永久删除文档。这两种方法都采用一个指定删除条件的过滤文档作为参数。例如,这将删除刚刚创建的电影:

js 复制代码
> db.movies.deleteOne({title : "Star Wars: Episode IV - A New Hope"})

使用 deleteMany 删除与过滤条件匹配的所有文档。

6、数据类型

6.1、基本数据类型

MongoDB 中的文档可以被认为是"类似于 JSON"的形式,因为它们在概念上和 JavaScript 中的对象非常相近。JSON 是一种简单的数据表示方式,其规范可以用一段话来描述,并且仅有 6 种数据类型。这有很多好处:易于理解、易于解析且易于记忆。另外,由于只有 null、布尔值、数字、字符串、数组和对象这几种类型,因此JSON 的表达能力比较有限。

尽管这些类型已经有很强的表现力,但对于大多数应用程序,特别是在使用数据库时,还有许多其他类型是至关重要的。例如,JSON 没有日期类型,这使得原本容易的日期处理变得很麻烦。而且 JSON 只有一个数字类型,因此没有办法区分浮点数和整数,更不用说区分 32位和 64 位的数字了。同样,JSON 也无法表示其他的常用类型,比如正则表达式或函数。

MongoDB 在保留了 JSON 基本键--值对特性的基础上,增加了对许多额外数据类型的支持。每种类型的具体表示方式因编程语言而异。下面会介绍 MongoDB 支持的一些通用类型,以及如何将它们表示为 shell 中文档的一部分。最常见的类型有以下几种。

null:null 类型用于表示空值或不存在的字段。

js 复制代码
{"x" : null}

布尔类型:布尔类型的值可以为 true 或者 false。

js 复制代码
{"x" : true}

数值类型:shell 默认使用 64 位的浮点数来表示数值类型。因此,下面的数值在 shell 中看起来是"正常"的:

js 复制代码
{"x" : 3.14}
{"x" : 3}

对于整数,可以使用 NumberInt 或 NumberLong类,它们分别表示 4 字节和 8 字节的有符号整数。

js 复制代码
{"x" : NumberInt("3")}
{"x" : NumberLong("3")}

字符串类型:任何 UTF-8 字符串都可以使用字符串类型来表示。

js 复制代码
{"x" : "foobar"}

日期类型:MongoDB 会将日期存储为 64 位整数,表示自Unix 纪元(1970 年 1 月 1 日)以来的毫秒数,不包含时区信息。

js 复制代码
{"x" : new Date()}

正则表达式:查询时可以使用正则表达式,语法与 JavaScript 的正则表达式语法相同。

js 复制代码
{"x" : /foobar/i}

数组类型:集合或者列表可以表示为数组。

js 复制代码
{"x" : ["a", "b", "c"]}

内嵌文档:文档可以嵌套其他文档,此时被嵌套的文档就成了父文档的值。

js 复制代码
{"x" : {"foo" : "bar"}}

Object ID:Object ID 是一个 12 字节的 ID,是文档的唯一标识。

js 复制代码
{"x" : ObjectId()}

还有一些不太常用但可能也会需要的类型。

二进制数据: 二进制数据是任意字节的字符串,不能通过 shell 操作。如果要将非 UTF-8 字符串存入数据库,那么使用二进制数据是唯一的方法。

代码:MongoDB 还可以在查询和文档中存储任意的JavaScript 代码:

js 复制代码
{"x" : function() { /* ... */ }}

最后,还有一些类型主要在内部使用(或被其他类型取代)​。这些将根据需要在文中特别说明。

6.2、日期

在 JavaScript 中,Date 类可以用作 MongoDB 的日期类型。创建日期对象时,应该调用 newDate(),而不是Date()。如果将构造函数作为函数进行调用(不包括new 的方式)​,那么返回的是日期的字符串表示,而不是 Date 对象。这个结果与 MongoDB 无关,是JavaScript 的机制决定的。如果不小心使用了 Date 的构造函数,那么最终可能会出现字符串和日期的混乱。字符串与日期无法匹配,反之亦然,因此这可能会导致删除、更新、查询等大部分操作出现问题。

shell 会根据本地时区的设置显示日期对象。不过,数据库中存储的日期仅为 Unix 纪元以来的毫秒数,并没有与之关联的时区信息。​(当然,时区信息可以存储为另一个键的值。​)

6.3、数组

数组是一组值,既可以作为有序对象(如列表、栈或队列)来操作,也可以作为无序对象(如集合)来操作。

在下面这个文档中,键 "things" 的值是一个数组:

js 复制代码
{"things" : ["pie", 3.14]}

从这个例子中能够看出,数组可以包含不同数据类型的元素(在本例中为一个字符串和浮点数)​。实际上,常规键--值对支持的任何类型都可以作为数组(甚至是嵌套数组)的值。

文档中的数组有一个非常好的特性,就是 MongoDB 能够"理解"其结构,并且知道如何深入数组内部对其内容执行操作。这允许我们对数组进行查询并使用其内容创建索引。例如,在前面的例子中,MongoDB 可以查询出"things" 数组中包含 3.14 这个元素的所有文档。如果这是一个经常会使用的查询,那么你甚至可以对 "things"这个键创建索引来提高查询速度。

MongoDB 还允许使用原子更新来修改数组的内容,比如进入数组并将值 "pie" 改为 pi。

6.4、内嵌文档

文档可以作为键的值,这称为内嵌文档。内嵌文档可以使数据组织的方式更加自然,而不仅仅是键--值对这样的扁平结构。

如果我们有一个表示某人的文档,并且希望存储此人的地址,那么可以将此信息保存在嵌入的 "address" 文档中:

js 复制代码
{
    "name" : "John Doe",
    "address" : {
        "street" : "123 Park Street",
        "city" : "Anytown",
        "state" : "NY"
    }
}

在本例中,"address" 键的值是一个内嵌文档,它有自己的 "street"、"city" 和 "state" 键--值对。

与数组一样,MongoDB"理解"内嵌文档的结构,并能够在其中创建索引、执行查询或进行更新。

稍后会深入讨论模式设计,但即使从这个简单的示例,也可以看到内嵌文档如何改变我们处理数据的方式。在关系数据库中,例子中的文档可能会被建模为两个不同的表(people 和 addresses)中两个单独的行。使用MongoDB 可以将 "address" 文档直接嵌入 "person" 文档中。因此,如果使用得当,则内嵌文档可以使信息的表示方式更加自然。

另外,MongoDB 可能会导致更多的数据重复。假设addresses 是关系数据库中一个单独的表,我们需要修正地址中的一个拼写错误。当对 people 和 addresses进行连接操作时,每个共享此地址的人的信息都会得到 更新。使用 MongoDB,则需要对每个人的文档中的这个拼写错误分别进行修正。

6.5、ObjectId和_id

MongoDB 中存储的每个文档都必须有一个 "_id"键。"_id" 的值可以是任何类型,但其默认为 ObjectId。在单个集合中,每个文档的 "_id" 值都必须是唯一的,以确保集合中的每个文档都可以被唯一标识。也就是说,如果你有两个集合,那么每个集合都可以有一个 "_id" 值为 123 的文档。但是,每个集合里面均只能有一个文档的 "_id" 值可以为 123。

6.5.1、ObjectId

ObjectId 是 "_id" 的默认类型。ObjectId 类采用了轻量化设计,可以很容易地在不同的机器上以全局唯一的方式生成。MongoDB 的分布式特性是它使用ObjectId 而不是其他传统做法(比如自动递增主键)的主要原因:跨多个服务器同步自动递增主键既困难又耗时。因为 MongoDB 的设计初衷就是作为一个分布式数据库,所以能够在分片环境中生成唯一的标识符非常重要。

ObjectId 占用了 12 字节的存储空间,可以用 24 个十六进制数字组成的字符串来表示:每字节存储两个数字。这会让它们看起来比实际大,很多人会因此感到紧张。但需要注意的是,尽管 ObjectId 通常被表示为一个巨大的十六进制字符串,但该字符串实际上是所存储数据的两倍长。

如果快速地连续创建多个新的 ObjectId,则会发现每次只能看到最后几个数字有变化。此外,如果在创建的过程中间隔几秒,那么 ObjectId 中间的几个数字也将发生变化。这是由 ObjectId 的创建方式导致的。ObjectId 的 12 字节是按照如下方式生成的:

js 复制代码
0  |  1  |  2  |  3  |  4  |  5  |  6  |  7  |  8  |  9  |  10  |  11
         时间戳      |           随机值            | 计数器(起始值随机)

ObjectId 的前 4 字节是从 Unix 纪元开始以秒为单位的时间戳。这提供了一些有用的属性。

  • 时间戳与接下来的 5 字节(稍后会介绍)组合在一起,在秒级别的粒度上提供了唯一性。
  • 因为时间戳在前,所以 ObjectId 将大致按照插入的顺序进行排列。这并不是一个很强的保证,但是确实在某些方面很有用,比如可以使 ObjectId 的索引效率更高。
  • 在这 4 字节中也隐含了每个文档的创建时间。大多数驱动程序提供了从 ObjectId 中提取此信息的方法。

由于 ObjectId 使用的是当前时间,因此很多人会担心需要对服务器进行时钟同步。尽管出于其他原因进行时钟同步是一个好主意(参见 24.5.1 节)​,但在这里实际的时间戳对 ObjectId 并不重要,只要它总是不停增加就可以了(每秒 1 次)​。

ObjectId 中接下来的 5 字节是一个随机值。最后 3字节是一个计数器,以一个随机数作为起始值,用来避免在不同机器上生成相互冲突的 ObjectId。

因此,前 9 字节保证了 ObjectId 在 1 秒内跨机器和进程的唯一性。最后 3 字节只是一个递增计数器,负责确保单个进程中 1 秒内的唯一性。这允许在 1秒内为每个进程生成多达 2563(16 777 216)个唯一的 ObjectId。

6.5.2、自动生成 _id

如前所述,如果插入文档时不存在 "_id" 键,则会自动创建一个并添加到插入的文档中。这个工作可以由MongoDB 的服务器端处理,但通常由客户端的驱动程序来完成。

7、使用MongoDB shell

尽管在上面的例子中只是连接到了一个本地的 mongod实例,但实际上可以将 shell 连接到机器可以访问的任何MongoDB 实例。要想连接到其他机器或端口上的mongod,需要在启动 shell 时指定主机名、端口和数据库:

js 复制代码
$ mongo some-host:30000/myDB
MongoDB shell version: 4.2.0
connecting to: some-host:30000/myDB
>

此时,db 就指向了 some-host:30000 上的 myDB 数据库。

有时在启动 mongo shell 时不连接任何 mongod 是很方便的。如果使用 --nodb 参数启动 shell,那么它在启动时就不会连接任何数据库:

js 复制代码
$ mongo --nodb
MongoDB shell version: 4.2.0
>

启动之后,可以在需要时运行 new Mongo("hostname")连接到 mongod:

js 复制代码
> conn = new Mongo("some-host:30000")
connection to some-host:30000
> db = conn.getDB("myDB")
myDB

执行完这两个命令之后,就可以正常使用 db 了。可以随时使用这些命令连接到不同的数据库或服务器端。

7.1、shell使用技巧

因为 mongo 就是一个 JavaScript 的 shell,所以通过查看 JavaScript 的在线文档可以获得大量帮助。对于MongoDB 特有的功能,shell 内置了帮助文档,可以输入 help 命令进行访问:

js 复制代码
> help
    db.help()                help on db methods
    db.mycoll.help()         help on collection methods
    sh.help()                sharding helpers
    ...

    show dbs                 show database names
    show collections         show collections in current database
    show users               show users in current database
    ...

可以使用 db.help() 查看数据库级别的帮助信息,使用db.foo.help() 查看集合级别的帮助信息。

有一个可以了解函数具体行为的好方法,就是在不使用小括号的情况下输入函数名。这样会打印出函数的JavaScript 源代码。如果想知道 update 函数的工作方式或是记不清参数的顺序,就可以执行以下操作。

js 复制代码
> db.movies.updateOne
function (filter, update, options) {
    var opts = Object.extend({}, options || {});

    // 检查update语句中的第一个键是否包含$
    var keys = Object.keys(update);
    if (keys.length == 0) {
        throw new Error("the update operation document must contain at
        least one atomic operator");
    }
    ...

7.2、使用shell执行脚本

除了以交互的方式使用 shell 之外,还可以将想要执行的JavaScript 文件传给 shell。在命令行中传入脚本即可:

js 复制代码
$ mongo script1.js script2.js script3.js
MongoDB shell version: 4.2.1
connecting to: mongodb://127.0.0.1:27017
MongoDB server version: 4.2.1

loading file: script1.js
I am script1.js
loading file: script2.js
I am script2.js
loading file: script3.js
I am script3.js
...

mongo shell 会依次执行传入的脚本并退出。

如果使用连接到非默认主机/端口上的 mongod 实例运行脚本,则需要先指定地址,之后再指定脚本:

js 复制代码
$ mongo server-1:30000/foo --quiet script1.js script2.js script3.js

这会让 db 设置为 server-1:30000 上的 foo 数据库,然后执行这 3 个脚本。

就如上面的脚本所示,可以在脚本中使用 print 函数将内容打印到标准输出。可以将 shell 作为命令管道的一部分来使用。如果将 shell 脚本的输出通过管道传给另一个命令,那么请使用 --quiet 选项来防止打印"MongoDBshell version v4.2.0"的提示信息。还可以使用 load 函数从交互式 shell 中运行脚本:

js 复制代码
> load("script1.js")
I am script1.js
true
>

在脚本中可以访问 db 变量以及任何其他全局变量。不过,shell 的辅助函数(如 use db 或 showcollections)不能在文件中使用。每一个辅助函数都有对应的 JavaScript 等价函数,如表所示。

还可以使用脚本将变量注入 shell 中,比如可以在脚本中简单地初始化一些常用的辅助函数。例如,以下脚本可能对本书的第三部分和第四部分内容非常有用。它定义了一个 connectTo 函数,该函数会连接到指定端口上本地运行的数据库,并将 db 指向该连接:

js 复制代码
// defineConnectTo.js

/**
 * 连接数据库并设置db变量
 */
var connectTo = function(port, dbname) {
    if (!port) {
        port = 27017;
    }

    if (!dbname) {
        dbname = "test";
    }

    db = connect("localhost:"+port+"/"+dbname);
    return db;
};

如果将这个脚本加载到 shell 中,connectTo 函数就可以使用了:

js 复制代码
> typeof connectTo
undefined
> load('defineConnectTo.js')
> typeof connectTo
function

除了添加辅助函数外,还可以使用脚本自动执行通常的任务和管理活动。

默认情况下,shell 会查找启动 shell 时所在的目录(使用 pwd() 查看)​。如果脚本不在当前目录下,则可以给shell 指定一个相对路径或绝对路径。如果希望将 shell脚本放在~/my-scripts 中,那么可以使用load("/home/myUser/my-scripts/defineConnectTo.js") 命令来加载defineConnectTo.js。注意,load 函数无法解析 ~ 字符。

可以使用 run 函数在 shell 中运行命令行程序。可以在函数的参数列表中指定程序所需的参数:

js 复制代码
> run("ls", "-l", "/home/myUser/my-scripts/")
sh70352| -rw-r--r-- 1 myUser myUser 2012-12-13 13:15 defineConnectTo.js
sh70532| -rw-r--r-- 1 myUser myUser 2013-02-22 15:10 script1.js
sh70532| -rw-r--r-- 1 myUser myUser 2013-02-22 15:12 script2.js
sh70532| -rw-r--r-- 1 myUser myUser 2013-02-22 15:13 script3.js

这种方式通常局限性较大,因为输出的格式很奇怪,而且不支持管道。

7.3、创建.mongorc.js文件

如果你有一些需要频繁被加载的脚本,那么可以将它们添加到 .mongorc.js 文件中。此文件会在启动 shell 时自动运行。

假设你希望 shell 在你成功登录时显示欢迎语。那么可以在用户主目录下创建一个名为 .mongorc.js 的文件,然后在其中添加如下内容:

js 复制代码
// .mongorc.js

var compliment = ["attractive", "intelligent", "like Batman"];
var index = Math.floor(Math.random()*3);

print("Hello, you're looking particularly "+compliment[index]+" today!");

之后,当启动 shell 时,就会看到这样一些内容:

js 复制代码
$ mongo
MongoDB shell version: 4.2.1
connecting to: test
Hello, you're looking particularly like Batman today!
>

在更实际一些的场景中,可以使用此脚本设置任何要使用的全局变量,或者为长名称创建一个简短的别名,也可以重写内置函数。.mongorc.js 最常见的用途之一是移除那些比较"危险"的 shell 辅助函数。可以在这里使用no 选项重写类似 dropDatabase 或 deleteIndexes 这样的函数,或者取消它们的定义:

js 复制代码
var no = function() {
    print("Not on my watch.");
};

// 禁止删除数据库
db.dropDatabase = DB.prototype.dropDatabase = no;

// 禁止删除集合
DBCollection.prototype.drop = no;

// 禁止删除单个索引
DBCollection.prototype.dropIndex = no;

// 禁止删除多个索引
DBCollection.prototype.dropIndexes = no;

现在,如果尝试调用这些函数,则它会打印出一条错误消息。注意,这种方式并不能保护数据库免受恶意用户的攻击,它只能帮助你防止自己的手误操作。

如果在启动 shell 时指定 --norc 参数,则可以禁用对.mongorc.js 文件的加载。

7.4、定制shell提示信息

通过将 prompt 变量设置为一个字符串或函数,可以重写默认的 shell 提示。如果正在运行的查询需要几分钟才能完成,则可能需要一个显示当前时间的提示,以便看到上次操作的完成时间:

js 复制代码
prompt = function() {
    return (new Date())+"> ";
};

另一个方便的提示是显示当前正在使用的数据库:

js 复制代码
prompt = function() {
    if (typeof db == 'undefined') {
        return '(nodb)> ';
    }

    // 检查最后的数据库操作
    try {
        db.runCommand({getLastError:1});
    } catch (e) {
        print(e);
    }

    return db+"> ";
};

注意,提示函数应该返回一个字符串,并且在捕获异常时要非常小心:如果提示中出现了异常,那么用户可能会非常困惑!

通常,提示函数应该包含对 getLastError 的调用。这样可以捕获写入时的错误,并在 shell 断开连接时自动重新连接(如果重新启动了 mongod 的话)​。

如果希望始终使用自定义提示(或者配置几个可以在shell 中进行切换的自定义提示)​,那么 .mongorc.js 文件是进行此设置的好地方。

7.5、编辑复杂变量

shell 的多行支持比较有限:不能编辑前面的行。如果编辑到第 15 行时发现第 1 行有一个错误,那会非常让人懊恼。因此,对于较大的代码块或者对象,可能需要在编辑器中编辑它们。为此,可以在 shell 中设置 EDITOR变量(也可以在环境变量中进行设置)​:

js 复制代码
> EDITOR="/usr/bin/emacs"

现在,如果要编辑一个变量,可以使用 edit varname 命令,如下所示:

js 复制代码
> var wap = db.books.findOne({title: "War and Peace"});
> edit wap

完成更改后,保存并退出编辑器。变量将被重新解析并加载回 shell。

将 EDITOR="/path/to/editor"; 添加到 .mongorc.js 文件中,以后就不用再设置此变量了。

7.6、不便使用的集合名称

大多数情况下可以使用 db.collectionName 语法来获得一个集合的内容,但如果集合名称是保留字或是无效的JavaScript 属性名称,那么此方法就不能正常工作了。

假设我们正在尝试访问 version 集合。我们不能使用db.version,因为 db.version 是 db 上的一个方法(它会返回正在运行的 MongoDB 服务器版本)​:

js 复制代码
> db.version
function () {
    return this.serverBuildInfo().version;
}

如果要访问 version 集合,则必须使用 getCollection 函数:

js 复制代码
> db.getCollection("version");
test.version

这种方式也可以用于在 JavaScript 属性名称中包含无效字符的集合名称,比如 foo-bar-baz 和123abc。​(JavaScript 属性名称只能包含字母、数字、$ 和 _,而且不能以数字开头。​)

另一种绕过无效属性的方式是使用数组访问语法。在JavaScript 中,x.y 等同于 x'y'​。这意味着除了名称的字面量之外,还可以使用变量访问子集合。因此,如果需要对 blog 的每个子集合执行某些操作,那么可以使用以下方法遍历它们:

js 复制代码
var collections = ["posts", "comments", "authors"];

for (var i in collections) {
      print(db.blog[collections[i]]);
}

而不需要这样:

js 复制代码
print(db.blog.posts);
print(db.blog.comments);
print(db.blog.authors);

注意,不能使用 db.blog.i,这样会被解释为test.blog.i,而不是 test.blog.posts。如果想将 i 解释为变量,则必须使用 db.blogi 语法。

可以使用下面这种技术来访问那些名字怪异的集合:

js 复制代码
> var name = "@#&!"
> db[name].find()

直接使用 db.@#&! 进行查询是非法的,但是可以使用dbname​。

相关推荐
吴声子夜歌3 小时前
MongoDB 4.2——查询
数据库·mongodb
xieliyu.4 小时前
MySQL 存储过程详解:概念、创建与删除全教程
开发语言·数据库·mysql
ttwuai5 小时前
AI 生成后台删除按钮后,MySQL 软删除和唯一索引怎么验
数据库·mysql·golang
张人玉5 小时前
基于 Vue 3 + ECharts + Express + SQLite 构建的新能源汽车销量数据分析与可视化平台——新能源汽车销量数据分析系统
数据库·vue.js·sqlite·echarts
笨笨饿7 小时前
#102_Codex无在VSCold无法打开
java·c语言·数据库·笔记
向夏威夷 梦断明暄7 小时前
从架构特点到功能缺陷,重新认识分析型分布式数据库
数据库·分布式·架构
不知疲倦的仄仄7 小时前
MySQL/Read View快照/MVCC/串行化
java·数据库·mysql
向夏威夷 梦断明暄7 小时前
C# 弃元模式:从语法糖到性能利器的深度解析
服务器·数据库·c#
糖果店的幽灵8 小时前
大模型测评DeepEval快速入门-RAG指标详解
数据库·人工智能·langgraph·大模型测评·deepeval