第一章 – 基础知识
最后更新于:2022-04-01 00:54:53
我们通过学习 MongoDB 的基本工作原理,开始我们的 MongoDB 之旅。当然,这是学习 MongoDB 的核心,它也能帮助我们回答诸如,MongoDB 适用于哪些场景这些更高层次的问题。
开始之前,这有六个简单的概念我们需要了解一下。
1. MongoDB中的 `database` 有着和你熟知的"数据库"一样的概念 (对 Oracle 来说就是 schema)。一个 MongoDB 实例中,可以有零个或多个数据库,每个都作为一个高等容器,用于存储数据。
2. 数据库中可以有零个或多个 `collections` (集合)。集合和传统意义上的 `table` 基本一致,你可以简单的把两者看成是一样的东西。
3. 集合是由零个或多个 `documents` (文档)组成。同样,一个文档可以看成是一 `row`。
4. 文档是由零个或多个 `fields` (字段)组成。, 没错,它就是 `columns`。
5. `Indexes` (索引)在 MongoDB 中扮演着和它们在 RDBMS 中一样的角色。
6. `Cursors` (游标)和上面的五个概念都不一样,但是它非常重要,并且经常被忽视,因此我觉得它们值得单独讨论一下。其中最重要的你要理解的一点是,游标是,当你问 MongoDB 拿数据的时候,它会给你返回一个结果集的指针而不是真正的数据,这个指针我们叫它游标,我们可以拿游标做我们想做的任何事情,比如说计数或者跨行之类的,而无需把真正的数据拖下来,在真正的数据上操作。
综上,MongoDB 是由包含 `collections` 的 `databases` 组成的。而 `collection` 是由 `documents`组成。每个 `document`是由 `fields` 组成。 `Collections` 可以被 `indexed`,以便提高查找和排序的性能。最后,当我们从 MongoDB 获取数据的时候,我们通过 `cursor` 来操作,读操作会被延迟到需要实际数据的时候才会执行。
那为什么我们需要新的术语(collection vs. table, document vs. row and field vs. column)?为了让看起来更复杂点?事实上,虽然这些概念和关系型数据中的概念类似,但是还是有差异的。核心差异在于,关系型数据库是在 `table` 上定义的`columns`,而面向文档数据库是在 `document` 上定义的 `fields`。也就是说,在 `collection` 中的每个 `document` 都可以有它自己独立的 `fields`。因此,对于 `collection` 来说是个简化了的 `table` ,但是一个 `document` 却比一 `row` 有更多的信息。
虽然这些概念很重要,但是如果现在搞不明白也不要紧。多插几条数据就明白上面说的到底是什么意思了。反正,要点就是,集合不对存储内容严格限制 (所谓的无模式(schema-less))。字段由每个独立的文档进行跟踪处理。这样做的优点和缺点将在下面章节一一讨论。
好了我们开始吧。如果你还没有运行 MongoDB,那么快去运行 `mongod` 服务和开启 mongo shell。shell 用的是 JavaScript。你可以试试一些全局命令,比如 `help` 或者 `exit`。如果要操作当前数据库,用 `db` ,比如 `db.help()` 或者`db.stats()`。如果要操作指定集合,大多数情况下我们会操作集合而不是数据库,用 `db.COLLECTION_NAME` ,比如`db.unicorns.help()` 或者 `db.unicorns.count()`。
我们继续,输入 `db.help()`,就能拿到一个对 `db` 能执行的所有的命令的列表。
顺便说一句:因为这是一个 JavaScript shell,如果你输入的命令漏了 `()`,你会看到这个命令的源码,而不是执行这个命令。我提一下,是为了避免你执行漏了括号的命令,拿到一个以 `function (...){` 开头的返回的时候,觉得神奇不可思议。比如说,如果你输入 `db.help` (不带括号), 你会看到 `help` 方法的内部实现。
首先我们用全局的 `use` 来切换数据库,继续,输入 `use learn`。这个数据库实际存在与否完全没有关系。我们在里面生成集合的时候, `learn` 数据库会自动建起来。现在,我们在一个数据库里面了,你可以开始尝试一下数据库命令,比如`db.getCollectionNames()`。执行之后,你会得到一个空数组 (`[ ]`)。因为集合是无模式的,我们不需要特地去配置它。我们可以简单的插入一个文档到一个新的集合。像这样,我们用 `insert` 命令,在文档中插入:
~~~
db.unicorns.insert({name: 'Aurora',
gender: 'f', weight: 450})
~~~
这行命令对集合 `unicorns` 执行了 `insert` 命令,并传入一个参数。MongoDB 内部用二进制序列化 JSON 格式,称为 BSON。外部,也就是说我们多数情况应该用 JSON,就像上面的参数一样。然后我们执行 `db.getCollectionNames()` ,我们将能拿到两个集合: `unicorns` 和 `system.indexes`。在每个数据库中都会有一个 `system.indexes` 集合,用来保存我们数据的的索引信息。
你现在可以对用 `unicorns` 执行 `find` 命令,然后返回文档列表:
~~~
db.unicorns.find()
~~~
请注意,除你指定的字段之外,会多出一个 `_id` 字段。每个文档都会有一个唯一 `_id` 字段。你可以自己生成一个,或者让 MongoDB 帮你生成一个 `ObjectId` 类型的。多数情况下,你会乐意让 MongoDB 帮你生成的。默认的 `_id` 字段是已被索引的 - 这就说明了为什么会有 `system.indexes` 集合。你可以看看 `system.indexes`:
~~~
db.system.indexes.find()
~~~
你可以看到索引的名字,被索引的数据库和集合,以及在索引中的字段。
现在,回到我们关于数组无模式的讨论中来。往 `unicorns` 插入一个完全不同的文档,比如:
~~~
db.unicorns.insert({name: 'Leto',
gender: 'm',
home: 'Arrakeen',
worm: false})
~~~
然后,再用 `find` 列出文档。等我们理解再深入一点的时候,将会讨论一下 MongoDB 的有趣行为。到这里,我希望你开始理解,为什么那些传统的术语在这里不适用了。
## [](https://github.com/geminiyellow/the-little-mongodb-book/blob/master/zh-cn/mongodb.markdown#掌握选择器selector)掌握选择器(Selector)
除了我们介绍过的六个概念,在开始讨论更深入的话题之前,MongoDB 还有一个应该掌握的实用概念:查询选择器。MongoDB 的查询选择器就像 SQL 语句里面的 `where` 一样。因此,你会在对集合的文档做查找,计数,更新,删除的时候用到它。选择器是一个 JSON 对象,最简单的是就是用 `{}` 匹配所有的文档。如果我们想找出所有母独角兽,我们可以用 `{gender:'f'}`。
开始深入学习选择器之前,让我们先做些准备。首先,把刚才我们插入 `unicorns` 集合的数据删除,通过:`db.unicorns.remove({})`。现在,再插入一些用来演示的数据 (你不会手打吧):
~~~
db.unicorns.insert({name: 'Horny',
dob: new Date(1992,2,13,7,47),
loves: ['carrot','papaya'],
weight: 600,
gender: 'm',
vampires: 63});
db.unicorns.insert({name: 'Aurora',
dob: new Date(1991, 0, 24, 13, 0),
loves: ['carrot', 'grape'],
weight: 450,
gender: 'f',
vampires: 43});
db.unicorns.insert({name: 'Unicrom',
dob: new Date(1973, 1, 9, 22, 10),
loves: ['energon', 'redbull'],
weight: 984,
gender: 'm',
vampires: 182});
db.unicorns.insert({name: 'Roooooodles',
dob: new Date(1979, 7, 18, 18, 44),
loves: ['apple'],
weight: 575,
gender: 'm',
vampires: 99});
db.unicorns.insert({name: 'Solnara',
dob: new Date(1985, 6, 4, 2, 1),
loves:['apple', 'carrot',
'chocolate'],
weight:550,
gender:'f',
vampires:80});
db.unicorns.insert({name:'Ayna',
dob: new Date(1998, 2, 7, 8, 30),
loves: ['strawberry', 'lemon'],
weight: 733,
gender: 'f',
vampires: 40});
db.unicorns.insert({name:'Kenny',
dob: new Date(1997, 6, 1, 10, 42),
loves: ['grape', 'lemon'],
weight: 690,
gender: 'm',
vampires: 39});
db.unicorns.insert({name: 'Raleigh',
dob: new Date(2005, 4, 3, 0, 57),
loves: ['apple', 'sugar'],
weight: 421,
gender: 'm',
vampires: 2});
db.unicorns.insert({name: 'Leia',
dob: new Date(2001, 9, 8, 14, 53),
loves: ['apple', 'watermelon'],
weight: 601,
gender: 'f',
vampires: 33});
db.unicorns.insert({name: 'Pilot',
dob: new Date(1997, 2, 1, 5, 3),
loves: ['apple', 'watermelon'],
weight: 650,
gender: 'm',
vampires: 54});
db.unicorns.insert({name: 'Nimue',
dob: new Date(1999, 11, 20, 16, 15),
loves: ['grape', 'carrot'],
weight: 540,
gender: 'f'});
db.unicorns.insert({name: 'Dunx',
dob: new Date(1976, 6, 18, 18, 18),
loves: ['grape', 'watermelon'],
weight: 704,
gender: 'm',
vampires: 165});
~~~
现在我们有数据了,我们可以开始来学习掌握选择器了。`{field: value}` 用来查找那些 `field` 的值等于 `value` 的文档。 `{field1: value1, field2: value2}` 相当于 `and` 查询。还有 `$lt`, `$lte`, `$gt`, `$gte` 和 `$ne` 被用来处理 小于,小于等于,大于,大于等于,和不等于操作。比如,获取所有体重大于700磅的公独角兽,我们可以这样:
~~~
db.unicorns.find({gender: 'm',
weight: {$gt: 700}})
//or (not quite the same thing, but for
//demonstration purposes)
db.unicorns.find({gender: {$ne: 'f'},
weight: {$gte: 701}})
~~~
`$exists` 用来匹配字段是否存在,比如:
~~~
db.unicorns.find({
vampires: {$exists: false}})
~~~
会返回一条文档。'$in' 被用来匹配查询文档在我们传入的数组参数中是否存在匹配值,比如:
~~~
db.unicorns.find({
loves: {$in:['apple','orange']}})
~~~
会返回那些喜欢 `apple` 或者 `orange` 的独角兽。
如果我们想要 OR 而不是 AND 来处理选择条件的话,我们可以用 `$or` 操作符,再给它一个我们要匹配的数组:
~~~
db.unicorns.find({gender: 'f',
$or: [{loves: 'apple'},
{weight: {$lt: 500}}]})
~~~
上面的查询会返回那些喜欢 `apples` 或者 `weigh` 小于500磅的母独角兽。
在我们最后两个例子里面有个非常赞的特性。你应该已经注意到了,`loves` 字段是个数组。MongoDB 允许数组作为基本对象(first class objects)处理。这是个令人难以置信的超赞特性。一旦你开始用它,你都不知道没了它你怎么活下去了。最有趣的是,基于数组的查询变得非常简单: `{loves: 'watermelon'}` 会把文档中 `loves` 中有 `watermelon` 的值全部查询出来。
除了我们介绍的这些,还有更多可用的操作。所有这些都记载在 MongoDB 手册上的 [Query Selectors](http://docs.mongodb.org/manual/reference/operator/query/#query-selectors) 这一章。我们介绍的仅仅是那些你学习时所需要用到的,同时也是你最经常用到的操作。
我们已经学习了选择器是如何配合 `find` 命令使用的了。还大致介绍了一下如何配合 `remove` 命令使用,`count` 命令虽然没介绍,不过你肯定知道应该怎么做,而 `update` 命令,之后我们会花多点时间来详细学习它。
MongoDB 为我们的 `_id` 字段生成的 `ObjectId` 可以这样查询:
~~~
db.unicorns.find(
{_id: ObjectId("TheObjectId")})
~~~
## [](https://github.com/geminiyellow/the-little-mongodb-book/blob/master/zh-cn/mongodb.markdown#小结)小结
我们还没有看到 `update` , 或是能拿来做更华丽事情的 `find`。不过,我们已经安装好 MongoDB 并运行起来了, 简略的介绍了一下 `insert` 和 `remove` 命令 (完整版也没比我们介绍的多什么)。 我们还介绍了 `find` 以及了解了 MongoDB`selectors` 是怎么一回事。 我们起了个很好的头,并为以后的学习奠定了坚实基础。 信不信由你,其实你已经掌握了学习 MongoDB 所必须的大多数知识 - 它真的是易学易用。 我强烈建议你在继续学习之前在本机上多试试多玩玩。 插入不同的文档,可以试试看在不同的集合中,习惯一下使用不同的选择器。试试 `find`, `count` 和 `remove`。 多试几次之后,你会发现原来看起来那么格格不入的东西,用起来居然水到渠成。