总结

最后更新于:2022-04-01 00:55:09

你现在应该有足够的能力开始在真实项目中使用 MongoDB 了。虽然 MongoDB 远不止我们学到的这些内容,但是你要作的下一步是,把学到的知识融会贯通,熟悉我们需要用到的功能。[MongoDB website](http://www.mongodb.org/) 有许多有用的信息。官网的[MongoDB user group](http://groups.google.com/group/mongodb-user) 是个问问题的好地方。 NoSQL 不光是为需求而生,它同时还是不断尝试创新的成果。不得不承认,我们的领域是不断前行的。如果我们不尝试,一旦失败,我们就绝不会取得成功。就是这样的,我认为,这是让你在职业生涯一路走好的方法。
';

第七章 – 性能和工具

最后更新于:2022-04-01 00:55:07

在这章中,我们来讲几个关于性能的话题,以及在 MongoDB 开发中用到的一些工具。我们不会深入其中的一个话题,不过我们会指出每个话题中最重要的方面。 ## [](https://github.com/geminiyellow/the-little-mongodb-book/blob/master/zh-cn/mongodb.markdown#索引index)索引(Index) 首先我们要介绍一个特殊的集合 `system.indexes` ,它保存了我们数据库中所有的索引信息。索引的作用在 MongoDB 中和关系型数据库基本一致: 帮助改善查询和排序的性能。创建索引用 `ensureIndex` : ~~~ // where "name" is the field name db.unicorns.ensureIndex({name: 1}); ~~~ 删除索引用 `dropIndex`: ~~~ db.unicorns.dropIndex({name: 1}); ~~~ 可以创建唯一索引,这需要把第二个参数 `unique` 设置为 `true`: ~~~ db.unicorns.ensureIndex({name: 1}, {unique: true}); ~~~ 索引可以内嵌到字段中 (再说一次,用点号) 和任何数组字段。我们可以这样创建复合索引: ~~~ db.unicorns.ensureIndex({name: 1, vampires: -1}); ~~~ 索引的顺序 (1 升序, -1 降序) 对单键索引不起任何影响,但它会在使用复合索引的时候有所不同,比如你用不止一个索引来进行排序的时候。 阅读 [indexes page](http://docs.mongodb.org/manual/indexes/) 获取更多关于索引的信息。 ## [](https://github.com/geminiyellow/the-little-mongodb-book/blob/master/zh-cn/mongodb.markdown#explain)Explain 需要检查你的查询是否用到了索引,你可以通过 `explain` 方法: ~~~ db.unicorns.find().explain() ~~~ 输出告诉我们,我们用的是 `BasicCursor` (意思是没索引), 12 个对象被扫描,用了多少时间,什么索引,如果有索引,还会有其他有用信息。 如果我们改变查询索引语句,查询一个有索引的字段,我们可以看到 `BtreeCursor` 作为索引被用到填充请求中去: ~~~ db.unicorns.find({name: 'Pilot'}).explain() ~~~ ## [](https://github.com/geminiyellow/the-little-mongodb-book/blob/master/zh-cn/mongodb.markdown#复制replication)复制(Replication) MongoDB 的复制在某些方面和关系型数据库的复制类似。所有的生产部署应该都是副本集,理想情况下,三个或者多个服务器都保持相同的数据。写操作被发送到单个服务器,也即主服务器,然后从它异步复制到所有的从服务器上。你可以控制是否允许从服务器上进行读操作,这可以让一些特定的查询从主服务器中分离出来,当然,存在读取到旧数据的风险。如果主服务器异常关闭,从服务中的一个将会自动晋升为新的主服务器继续工作。另外,MongoDB 的复制不在本书的讨论范围之内。 ## [](https://github.com/geminiyellow/the-little-mongodb-book/blob/master/zh-cn/mongodb.markdown#分片sharding)分片(Sharding) MongoDB 支持自动分片。分片是实现数据扩展的一种方法,依靠在跨服务器或者集群上进行数据分区来实现。一个最简单的实现是把所有的用户数据,按照名字首字母 A-M 放在服务器 1 ,然后剩下的放在服务器 2。谢天谢地,MongoDB 的拆分能力远比这种分法要强。分片不在本书的讨论范围之内,不过你应当有分片的概念,并且,当你的需求增长超过了使用单一副本集的时候,你应该考虑它。 尽管复制有时候可以提高性能(通过将长时间查询隔离到从服务器,或者降低某些类型的查询的延迟),它的主要目的是维护高可用性。分片是扩展 MongoDB 集群的主要方法。把复制和分片结合起来实现可扩展和高可用性是禁术。 ## [](https://github.com/geminiyellow/the-little-mongodb-book/blob/master/zh-cn/mongodb.markdown#状态stats)状态(Stats) 你可以通过 `db.stats()` 查询数据库的状态。基本上都是关于数据库大小的信息。你还可以查询集合的状态,比如说`unicorns` 集合,可以输入 `db.unicorns.stats()`。基本上都是关于集合大小的信息,以及集合的索引信息。 ## [](https://github.com/geminiyellow/the-little-mongodb-book/blob/master/zh-cn/mongodb.markdown#分析器profiler)分析器(Profiler) 你可以这样执行 MongoDB profiler : ~~~ db.setProfilingLevel(2); ~~~ 启动之后,我们可以执行一个命令: ~~~ db.unicorns.find({weight: {$gt: 600}}); ~~~ 然后检查 profiler: ~~~ db.system.profile.find() ~~~ 输出会告诉我们:什么时候执行了什么,有多少文档被扫描,有多少数据被返回。 你要停止 profiler 只需要再调用一次 `setProfilingLevel` ,不过这次参数是 `0`。指定 `1` 作为第一个参数,将会过滤统计超过 100 milliseconds 的任务. 100 milliseconds 是默认的阈值,你可以在第二个参数中,指定不同的阈值时间,以 milliseconds 为单位: ~~~ //profile anything that takes //more than 1 second db.setProfilingLevel(1, 1000); ~~~ ## [](https://github.com/geminiyellow/the-little-mongodb-book/blob/master/zh-cn/mongodb.markdown#备份和还原)备份和还原 在 MongoDB 的 `bin` 目录下有一个可执行文件 `mongodump` 。简单执行 `mongodump` 会链接到 localhost 并备份你所有的数据库到 `dump` 子目录。你可以用 `mongodump --help` 查看更多执行参数。常用的参数有 `--db DBNAME` 备份指定数据库和`--collection COLLECTIONNAME` 备份指定集合。你可以用 `mongorestore` 可执行文件,同样在 `bin` 目录下,还原之前的备份。同样, `--db` 和 `--collection` 可以指定还原的数据库和/或集合。 `mongodump` 和 `mongorestore` 使用 BSON,这是 MongoDB 的原生格式。 比如,来备份我们的 `learn` 数据库导 `backup` 文件夹,我们需要执行(在控制台或者终端中执行该命令,而不是在 mongo shell 中): ~~~ mongodump --db learn --out backup ~~~ 如果只还原 `unicorns` 集合,我们可以这样做: ~~~ mongorestore --db learn --collection unicorns \ backup/learn/unicorns.bson ~~~ 值得一提的是, `mongoexport` 和 `mongoimport` 是另外两个可执行文件,用于导出和从 JSON/CSV 格式文件导入数据。比如说,我们可以像这样导出一个 JSON: ~~~ mongoexport --db learn --collection unicorns ~~~ CSV 格式是这样: ~~~ mongoexport --db learn \ --collection unicorns \ --csv --fields name,weight,vampires ~~~ 注意 `mongoexport` 和 `mongoimport` 不一定能正确代表数据。真实的备份中,只能使用 `mongodump` 和 `mongorestore` 。 你可以从 MongoDB 手册中读到更多的 [备份须知](http://docs.mongodb.org/manual/core/backups/) 。 ## [](https://github.com/geminiyellow/the-little-mongodb-book/blob/master/zh-cn/mongodb.markdown#小结-6)小结 在这章中我们介绍了 MongoDB 的各种命令,工具和性能细节。我们没有涉及所有的东西,不过我们已经把常用的都看了一遍。MongoDB 的索引和关系型数据库中的索引非常类似,其他一些工具也一样。不过,在 MongoDB 中,这些更易于使用。
';

第六章 – 数据聚合

最后更新于:2022-04-01 00:55:05

## 聚合管道(Aggregation Pipeline) 聚合管道提供了一种方法用于转换整合文档到集合。你可以通过管道来传递文档,就像 Unix 的 "pipe" 一样,将一个命令的输出传递到另第二个,第三个,等等。 最简单的聚合,应该是你在 SQL 中早已熟悉的 `group by` 操作。我们已经看过 `count()` 方法,那么假设我们怎么才能知道有多少匹公独角兽,有多少匹母独角兽呢? ~~~ db.unicorns.aggregate([{$group:{_id:'$gender', total: {$sum:1}}}]) ~~~ 在 shell 中,我们有 `aggregate` 辅助类,用来执行数组的管道操作。对于简单的对某物进行分组计数,我们只需要简单的调用 `$group`。这和 SQL 中的 `GROUP BY` 完全一致,我们用来创建一个新的文档,以 `_id` 字段表示我们以什么来分组(在这里是以 `gender`) ,另外的字段通常被分配为聚合的结果,在这里,我们对匹配某一性别的各文档使用了 `$sum` 1 。你应该注意到了 `_id` 字段被分配为 `'$gender'` 而不是 `'gender'` - 字段前面的 `'$'` 表示,该字段将会被输入的文档中的有同样名字的值所代替,一个占位符。 我们还可以用其他什么管道操作呢?在 `$group` 之前(之后也很常用)的一个是 `$match` - 这和 `find` 方法完全一样,允许我们获取文档中某个匹配的子集,或者在我们的结果中对文档进行筛选。 ~~~ db.unicorns.aggregate([{$match: {weight:{$lt:600}}}, {$group: {_id:'$gender', total:{$sum:1}, avgVamp:{$avg:'$vampires'}}}, {$sort:{avgVamp:-1}} ]) ~~~ 这里我们介绍另外一个管道操作 `$sort` ,作用和你想的完全一致,还有和它一起用的 `$skip` 和 `$limit`。以及用`$group` 操作 `$avg`。 MongoDB 数组非常强大,并且他们不会阻止我们往保存中的数组中写入内容。我们需要可以 "flatten" 他们以便对所有的东西进行计数: ~~~ db.unicorns.aggregate([{$unwind:'$loves'}, {$group: {_id:'$loves', total:{$sum:1}, unicorns:{$addToSet:'$name'}}}, {$sort:{total:-1}}, {$limit:1} ]) ~~~ 这里我们可以找出独角兽最喜欢吃的食物,以及拿到独角兽们喜欢吃的食物名单。 `$sort` 和 `$limit` 的组合能让你拿到 "top N" 这种查询的结果。 还有另外一个强大的管道操作叫做 [`$project`](http://docs.mongodb.org/manual/reference/operator/aggregation/project/#pipe._S_project) (类似于 `find`),不但允许你拿到指定字段,还可以根据现存字段进行创建或计算一个新字段。比如,可以用数学操作,在做平均运算之前,对几个字段进行加法运算,或者你可以用字符串操作创建一个新的字段,用于拼接现有字段。 这只是用聚合所能做到的众多功能中的皮毛, 2.6 的聚合拥有了更强大的力量,比如聚合命令可以返回结果集的游标(我们已经在第一章学过了) 或者可以将结果写到另外一个新集合中,通过 `$out` 管道操作。你可以从 [MongoDB 手册](http://docs.mongodb.org/manual/core/aggregation-pipeline/) 得到关于管道操作和表达式操作更多的例子。 ## [](https://github.com/geminiyellow/the-little-mongodb-book/blob/master/zh-cn/mongodb.markdown#mapreduce)MapReduce MapReduce 分两步进行数据处理。首先是 map,然后 reduce。在 map 步骤中,转换输入文档和输出一个 key=>value 对(key 和/或 value 可以很复杂)。然后, key/value 对以 key 进行分组,有同样的 key 的 value 会被收入一个数组中。在 reduce 步骤中,获取 key 和该 key 的 value 的数组,生成最终结果。map 和 reduce 方法用 JavaScript 来编写。 在 MongoDB 中我们对一个集合使用 `mapReduce` 命令。 `mapReduce` 执行 map 方法, reduce 方法和 output 指令。在我们的 shell 中,我们可以创建输入一个 JavaScript 方法。许多库中,支持字符串方法 (有点丑)。第三个参数设置一个附加参数,比如说我们可以过滤,排序和限制那些我们想要分析的文档。我们也可以提供一个 `finalize` 方法来处理 `reduce` 步骤之后的结果。 在你的大多数聚合中,也许无需用到 MapReduce , 但如果需要,你可以读到更多关于它的内容,从 [我的 blog](http://openmymind.net/2011/1/20/Understanding-Map-Reduce/) 和 [MongoDB 手册](http://docs.mongodb.org/manual/core/map-reduce/)。 ## [](https://github.com/geminiyellow/the-little-mongodb-book/blob/master/zh-cn/mongodb.markdown#小结-5)小结 在这章中我们介绍了 MongoDB 的 [聚合功能(aggregation capabilities)](http://docs.mongodb.org/manual/aggregation/)。 一旦你理解了聚合管道(Aggregation Pipeline)的构造,它还是相对容易编写的,并且它是一个聚合数据的强有力工具。 MapReduce 更难理解一点,不过它强力无边,就像你用 JavaScript 写的代码一样。
';

第五章 – MongoDB 适用场景

最后更新于:2022-04-01 00:55:02

现在你应该有感觉,何时何地把 MongoDB 融入你现有的系统是最棒的了。这有超多的新的类似的存储技术,肯定会让你在选择的时候晕头转向。 对我来说,最重要的教训,跟 MongoDB 无关,是说你不用再依赖单一的解决案来处理你的数据了。毫无疑问,一个单一的解决案有明显的优势,对于许多项目来说 - 或者说大多数 - 单一解决案是一个明智的选择。意思不是说你 _必须_ 使用不同的技术,而是说你 _可以_。 只有你自己才知道,引进新技术是否利大于弊。 说了那么多,我希望你到目前为止学到知识让你觉得 MongoDB 是一个通用的解决案。我们已经提到很多次了,面向文档的数据库和关系型数据库有很多方面类似。因此,与其绕开这些相同点,不如我们可以简单的这样认为, MongoDB 是关系型数据库的一个代替案。比如说用 Lucene 作为关系型数据库的全文检索索引的加强,或者用 Redis 作为持久型 key-value 存储,MongoDB 就是用来保存你的数据的。 注意,我没有说用 MongoDB _取代_ 关系型数据库,而是 _代替_ 案。它能做的有很多工具也能做。有些事情 MongoDB 可以做的更好,另外一些 MongoDB 做得差点。我们来进一步来讨论一下。 ## [](https://github.com/geminiyellow/the-little-mongodb-book/blob/master/zh-cn/mongodb.markdown#无模式flexible-schema)无模式(Flexible Schema) 面向文档数据库经常吹嘘的一个好处就是,它不需要一个固定的模式。这使得他们比传统的数据库表要灵活得多。我同意无模式是一个很不错的特性,但不是大多数人说的那样。 人们讲到无模式的时候,好像你就会把一堆乱七八糟的数据统统存起来一样。确实有些领域有些数据用关系型数据库来建模很痛苦,不过我觉得这些都是不常见的特例。无模式是酷,可是大多数情况下你的数据结构还是应当好好设计的。真正需要处理混乱时是不错,比如当你添加一个新功能的时候,不过事实是,大多数情况下,一个空列基本可以解决问题。 对我来说,动态模式的真正好处在于无需很多设置以及可以降低在 OOP 中使用的阻力。这在你使用静态语言的时候尤其明显。我在 C# 和 Ruby 中用过 MongoDB ,差异非常明显。Ruby 的动态特性以及它的流行的 ActiveRecord 实现,已经大幅降低面向对象/关系开发之间差异所带来的阻力。这不是说 MongoDB 和 Ruby 不配,而是是说它们太配了。真的,我觉得许多 Ruby 开发者眼中的的 MongoDB 只是有些许改进而已,而在 C# 或者 Java 开发者眼中,MongoDB 带来的是处理数据交互方式的翻天覆地变化。 假设从驱动开发者角度来看这个问题。你想保存一个对象?把它串行化成 JSON (严格来说是 BSON, 不过差不多) 然后把它传给 MongoDB。不需要做任何属性映射或者类型映射。这种简单性的好处就这样传递给了你,终端开发者。 ## [](https://github.com/geminiyellow/the-little-mongodb-book/blob/master/zh-cn/mongodb.markdown#写操作writes)写操作(Writes) MongoDB 可以胜任的一个特殊角色是在日志领域。有两点使得 MongoDB 的写操作非常快。首先,你可以选择发送了写操作命令之后立刻返回,而无须等到操作完成。其次,你可以控制数据持久性的写行为。这些设置,加上,可以定义一个成功的提交,需要在多少台服务器上成功拿到你的数据之后才算成功,并且每个写操作都是可设置, 这就给予你很高的权限用以控制写性能和数据持久性。 除了这些性能因素,日志数据还是这样一种数据集,用无模式集合更有优势。最后,MongoDB 还提供了 [受限集合(capped collection)](http://docs.mongodb.org/manual/core/capped-collections/)。到目前为止,所有我们默认创建的集合都是普通集合。我们可以通过 `db.createCollection` 命令来创建一个受限集合并标记它的限制: ~~~ //limit our capped collection to 1 megabyte db.createCollection('logs', {capped: true, size: 1048576}) ~~~ 当我们的受限集合到达 1MB 上限的时候,旧文档会被自动清除。另外一种限制可以基于文档个数,而不是大小,用 `max`标记。受限集合有一些非常有趣的属性。比如说,你可以更新文档但是你不能改变它的大小。插入顺序是被设置好了的,因此不需要另外提供一个索引来获取基于时间的排序,你可以 "tail" 一个受限集合,就和你在 Unix 中通过 `tail -f ` 来处理文件一样,获取最新的数据,如果存在数据的话,而不需要重新查询它。 如果想让你的数据 "过期" ,基于时间而不是整个集合的大小,你可以用 [TTL 索引](http://docs.mongodb.org/manual/tutorial/expire-data/) ,所谓 TTL 是 "time-to-live" 的缩写。 ## [](https://github.com/geminiyellow/the-little-mongodb-book/blob/master/zh-cn/mongodb.markdown#持久性durability)持久性(Durability) 在 1.8 之前的版本,MongoDB 不支持单服务器持久性。就是说,如果一个服务器崩溃了,可能会导致数据的丢失或者损坏。解决案是在多服务器上运行 MongoDB 副本 (MongoDB 支持复制)。日志(Journaling)是 1.8 版追加的一个非常重要的功能。从 2.0 版的 MongoDB 开始,日志是默认启动的,该功能允许快速恢复服务器,比如遭遇到了服务器崩溃或者停电的情况。 持久性在这里只是提一下,因为围绕 MongoDB 过去缺乏单服务器持久的问题,人们取得了众多成果。这个话题在以后的 Google 检索中也许还会继续出现。但是关于缺少日志功能这一缺点的信息,都是过时了的。 ## [](https://github.com/geminiyellow/the-little-mongodb-book/blob/master/zh-cn/mongodb.markdown#全文检索full-text-search)全文检索(Full Text Search) 真正的全文检索是在最近加入到 MongoDB 中的。它支持十五国语言,支持词形变化(stemming)和干扰字(stop words)。除了原生的 MongoDB 的全文检索支持,如果你需要一个更强大更全面的全文检索引擎的话,你需要另找方案。 ## [](https://github.com/geminiyellow/the-little-mongodb-book/blob/master/zh-cn/mongodb.markdown#事务transactions)事务(Transactions) MongoDB 不支持事务。这有两个代替案,一个很好用但有限制,另外一个比较麻烦但灵活。 第一个方案,就是各种原子更新操作。只要能解决你的问题,都挺不错。我们已经看过几个简单的了,比如 `$inc` 和`$set`。还有像 `findAndModify` 命令,可以更新或删除文档之后,自动返回修改过的文档。 第二个方案,当原子操作不能满足的时候,回到两段提交上来。对于事务,两段提交就好像给链接手工解引用。这是一个和存储无关的解决方案。两段提交实际上在关系型数据库世界中非常常用,用来实现多数据库之间的事务。 MongoDB 网站 [有个例子](http://docs.mongodb.org/manual/tutorial/perform-two-phase-commits/) 演示了最典型的场合 (资金转账)。通常的想法是,把事务的状态保存到实际的原子更新的文档中,然后手工的进行 init-pending-commit/rollback 处理。 MongoDB 支持内嵌文档以及它灵活的 schema 设计,让两步提交没那么痛苦,但是它仍然不是一个好处理,特别是当你刚开始接触它的时候。 ## [](https://github.com/geminiyellow/the-little-mongodb-book/blob/master/zh-cn/mongodb.markdown#数据处理data-processing)数据处理(Data Processing) 在2.2 版本之前的 MongoDB 依赖 MapReduce 来解决大部分数据处理工作。在 2.2 版本,它追加了一个强力的功能,叫做[aggregation framework or pipeline](http://docs.mongodb.org/manual/core/aggregation-pipeline/),因此你只要对那些尚未支持管道的,需要使用复杂方法的,不常见的聚合使用 MapReduce。下一章我们将看看聚合管道和 MapReduce 的细节。现在,你可以把他们想象成功能强大的,用不同方法实现的 `group by` (打个比方)。对于非常大的数据的处理,你可能要用到其他的工具,比如 Hadoop。值得庆幸的是,这两个系统是相辅相成的,这里有个 [MongoDB connector for Hadoop](http://docs.mongodb.org/ecosystem/tools/hadoop/)。 当然,关系型数据库也不擅长并行数据处理。MongoDB 有计划在未来的版本中,改善增加处理大数据集的能力。 ## [](https://github.com/geminiyellow/the-little-mongodb-book/blob/master/zh-cn/mongodb.markdown#地理空间查询geospatial)地理空间查询(Geospatial) 一个很强大的功能就是 MongoDB 支持 [geospatial 索引](http://docs.mongodb.org/manual/applications/geospatial-indexes/)。这允许你保存 geoJSON 或者 x 和 y 坐标到文档,并查询文档,用如 `$near` 来获取坐标集,或者 `$within` 来获取一个矩形或圆中的点。这个特性最好通过一些可视化例子来演示,所以如果你想学更多的话,可以试试看 [5 minute geospatial interactive tutorial](http://mongly.openmymind.net/geo/index)。 ## [](https://github.com/geminiyellow/the-little-mongodb-book/blob/master/zh-cn/mongodb.markdown#工具和成熟度)工具和成熟度 你应该已经知道这个问题的答案了,MongoDB 确实比大多数的关系型数据要年轻很多。这个问题确实是你应当考虑的,但是到底有多重要,这取决于你要做什么,怎么做。不管怎么说,一个好的评估,不可能忽略 MongoDB 年轻这一事实,而可用的工具也不是很好 (虽然成熟的关系型数据库工具有些也非常渣!)。举个例子,它缺乏对十进制浮点数的支持,在处理货币的系统来说,明显是一个问题 (尽管也不是致命的) 。 积极的一方面,它为大多数语言提供了驱动,协议现代而简约,开发速度相当快。MongoDB 被众多公司用到了生产环境中,虽然有所担心,但经过验证后,担心很快就变成了过去。 ## [](https://github.com/geminiyellow/the-little-mongodb-book/blob/master/zh-cn/mongodb.markdown#小结-4)小结 本章要说的是,MongoDB,大多数情况下,可以取代关系型数据库。它更简单更直接;更快速并且通常对应用开发者的约束更少。不过缺乏事务支持也许值得慎重考虑。当人们说起 _MongoDB 在新的数据库阵营中到底处在什么位置?_ 时,答案很简单: **中庸**(_2_)。
';

第四章 – 数据建模

最后更新于:2022-04-01 00:55:00

让我们换换思维,对 MongoDB 进行一个更抽象的理解。介绍一些新的术语和一些新的语法是非常容易的。而要接受一个以新的范式来建模,是相当不简单的。事实是,当用新技术进行建模的时候,我们中的许多人还在找什么可用的什么不可用。在这里我们只是开始新的开端,而最终你需要去在实战中练习和学习。 与大多数 NoSQL 数据库相比,面向文档型数据库和关系型数据库很相似 - 至少,在建模上是这样的。但是,不同点非常重要。 ## [](https://github.com/geminiyellow/the-little-mongodb-book/blob/master/zh-cn/mongodb.markdown#no-joins)No Joins 你需要适应的第一个,也是最根本的区别就是 mongoDB 没有链接(join) 。我不知道 MongoDB 中不支持链接的具体原因,但是我知道链接基本上意味着不可扩展。就是说,一旦你把数据水平扩展,无论如何你都要放弃在客户端(应用服务器)使用链接。事实就是,数据 _有_ 关系, 但 MongoDB 不支持链接。 没别的办法,为了在无连接的世界生存下去,我们只能在我们的应用代码中自己实现链接。我们需要进行二次查询 `find`,把相关数据保存到另一个集合中。我们设置数据和在关系型数据中声明一个外键没什么区别。先不管我们那美丽的`unicorns` 了,让我们来看看我们的 `employees`。 首先我们来创建一个雇主 (我提供了一个明确的 `_id` ,这样我们就可以和例子作成一样) ~~~ db.employees.insert({_id: ObjectId( "4d85c7039ab0fd70a117d730"), name: 'Leto'}) ~~~ 然后让我们加几个工人,把他们的管理者设置为 `Leto`: ~~~ db.employees.insert({_id: ObjectId( "4d85c7039ab0fd70a117d731"), name: 'Duncan', manager: ObjectId( "4d85c7039ab0fd70a117d730")}); db.employees.insert({_id: ObjectId( "4d85c7039ab0fd70a117d732"), name: 'Moneo', manager: ObjectId( "4d85c7039ab0fd70a117d730")}); ~~~ (有必要再重复一次, `_id` 可以是任何形式的唯一值。因为你很可能在实际中使用 `ObjectId` ,我们也在这里用它。) 当然,要找出 Leto 的所有工人,只需要执行: ~~~ db.employees.find({manager: ObjectId( "4d85c7039ab0fd70a117d730")}) ~~~ 这没什么神奇的。在最坏的情况下,大多数的时间,为弥补无链接所做的仅仅是增加一个额外的查询(可能是被索引的)。 ## [](https://github.com/geminiyellow/the-little-mongodb-book/blob/master/zh-cn/mongodb.markdown#数组和内嵌文档)数组和内嵌文档 MongoDB 不支持链接不意味着它没优势。还记得我们说过 MongoDB 支持数组作为文档中的基本对象吗?这在处理多对一(many-to-one)或者多对多(many-to-many)的关系的时候非常方便。举个简单的例子,如果一个工人有两个管理者,我们只需要像这样存一下数组: ~~~ db.employees.insert({_id: ObjectId( "4d85c7039ab0fd70a117d733"), name: 'Siona', manager: [ObjectId( "4d85c7039ab0fd70a117d730"), ObjectId( "4d85c7039ab0fd70a117d732")] }) ~~~ 有趣的是,对于某些文档,`manager` 可以是单个不同的值,而另外一些可以是数组。而我们原来的 `find` 查询依旧可用: ~~~ db.employees.find({manager: ObjectId( "4d85c7039ab0fd70a117d730")}) ~~~ 你会很快就发现,数组中的值比多对多链接表(many-to-many join-tables)要容易处理得多。 数组之外,MongoDB 还支持内嵌文档。来试试看向文档插入一个内嵌文档,像这样: ~~~ db.employees.insert({_id: ObjectId( "4d85c7039ab0fd70a117d734"), name: 'Ghanima', family: {mother: 'Chani', father: 'Paul', brother: ObjectId( "4d85c7039ab0fd70a117d730")}}) ~~~ 像你猜的那样,内嵌文档可以用 dot-notation 查询: ~~~ db.employees.find({ 'family.mother': 'Chani'}) ~~~ 我们只简单的介绍一下内嵌文档适用情况,以及你怎么使用它们。 结合两个概念,我们甚至可以内嵌文档数组: ~~~ db.employees.insert({_id: ObjectId( "4d85c7039ab0fd70a117d735"), name: 'Chani', family: [ {relation:'mother',name: 'Chani'}, {relation:'father',name: 'Paul'}, {relation:'brother', name: 'Duncan'}]}) ~~~ ## [](https://github.com/geminiyellow/the-little-mongodb-book/blob/master/zh-cn/mongodb.markdown#反规范化denormalization)反规范化(Denormalization) 另外一个代替链接的方案是对你的数据做反规范化处理(denormalization)。从历史角度看,反规范化处理是为了解决那些对性能敏感的问题,或是需要做快照的数据(比如说审计日志)。但是,随着日益增长的普及的 NoSQL,对链接的支持的日益丧失,反规范化作为规范化建模的一部分变得越来越普遍了。这不意味着,应该对你文档里的每条数据都做冗余处理。而是说,与其对冗余数据心存恐惧,让它影响你的设计决策,不如在建模的时候考虑什么信息应当属于什么文档。 比如说,假设你要写一个论坛应用。传统的方式是通过 `posts` 中的 `userid` 列,来关联一个特定的 `user` 和一篇 `post`。这样的建模,你没法在显示 `posts` 的时候不查询 (链接到) `users`。一个代替案是简单的在每篇 `post` 中把 `name` 和`userid` 一起保存。你可能要用到内嵌文档,比如 `user: {id: ObjectId('Something'), name: 'Leto'}`。是的,如果你让用户可以更新他们的名字,那么你得对所有的文档都进行更新(一个多重更新)。 适应这种方法不是对任何人都那么简单的。很多情况下这样做甚至是无意义的。不过不要害怕去尝试。它只是在某些情况下不适用而已,但在某些情况下是最好的解决方法。 ## [](https://github.com/geminiyellow/the-little-mongodb-book/blob/master/zh-cn/mongodb.markdown#你的选择是)你的选择是? 在处理一对多(one-to-many)或者多对多(many-to-many)场景的时候,id 数组通常是一个正确的选择。但通常,新人开发者在面对内嵌文档和 "手工" 引用时,左右为难。 首先,你应该知道的是,一个独立文档的大小当前被限制在 16MB 。知道了文档的大小限制,挺宽裕的,对你考虑怎么用它多少有些影响。在这点上,看起来大多数开发者都愿意手工维护数据引用关系。内嵌文档经常被用到,大多数情况下多是很小的数据块,那些总是被和父节点一起拉取的数据块。现实的例子是为每个用户保存一个 `addresses` ,看起来像这样: ~~~ db.users.insert({name: 'leto', email: 'leto@dune.gov', addresses: [{street: "229 W. 43rd St", city: "New York", state:"NY",zip:"10036"}, {street: "555 University", city: "Palo Alto", state:"CA",zip:"94107"}]}) ~~~ 这并不意味着你要低估内嵌文档的能力,或者仅仅把他们当成小技巧。把你的数据模型直接映射到你的对象,这会使得问题更简单,并且通常也不需要用到链接了。尤其是,当你考虑到 MongoDB 允许你对内嵌文档和数组的字段进行查询和索引时,效果特别明显。 ## [](https://github.com/geminiyellow/the-little-mongodb-book/blob/master/zh-cn/mongodb.markdown#大而全还是小而专的集合)大而全还是小而专的集合? 由于对集合没做任何的强制要求,完全可以在系统中用一个混合了各种文档的集合,但这绝对是个非常烂的主意。大多数 MongoDB 系统都采用了和关系型数据库类似的结构,分成几个集合。换而言之,如果在关系型数据库中是一个表,那么在 MongoDB 中会被作成一个集合 (many-to-many join tables being an important exception as well as tables that exist only to enable one to many relationships with simple entities)。 当你把内嵌文档考虑进来的时候,这个话题会变的更有趣。常见的例子就是博客。你是应该分成一个 `posts` 集合和一个`comments` 集合呢,还是应该每个 `post` 下面嵌入一个 `comments` 数组? 先不考虑那个 16MB 文档大小限制 ( _哈姆雷特_全文也没超过 200KB,所以你的博客是有多人气?),许多开发者都喜欢把东西划分开来。这样更简洁更明确,给你更好的性能。MongoDB 的灵活架构允许你把这两种方式结合起来,你可以把评论放在独立的集合中,同时在博客帖子下嵌入一小部分评论 (比如说最新评论) ,以便和帖子一同显示。这遵守以下的规则,就是你到想在一次查询中获取到什么内容。 这没有硬性规定(好吧,除了16MB限制)。尝试用不同的方法解决问题,你会知道什么能用什么不能用。 ## [](https://github.com/geminiyellow/the-little-mongodb-book/blob/master/zh-cn/mongodb.markdown#小结-3)小结 本章目标是提供一些对你在 MongoDB 中数据建模有帮助的指导, 一个新起点,如果愿意你可以这样认为。在一个面向文档系统中建模,和在面向关系世界中建模,是不一样的,但也没多少不同。你能得到更多的灵活性并且只有一个约束,而对于新系统,一切都很完美。你唯一会做错的就是你不去尝试。
';

第三章 – 掌握查询

最后更新于:2022-04-01 00:54:58

在第一章中我们对 `find` 命令做了一个初步的了解。除了 `selectors` 以外 `find` 还有更丰富的功能。我们已经说过,`find` 返回的结果是一个 `cursor`。我们将进一步看看它到底是什么意思。 ## [](https://github.com/geminiyellow/the-little-mongodb-book/blob/master/zh-cn/mongodb.markdown#字段选择)字段选择 在开始 `cursors` 的话题之前,你应该知道 `find` 有第二个可选参数,叫做 "projection"。这个参数是我们要检索或者排除字段的列表。比如,我们可以仅查询返回独角兽的名字而不带别的字段: ~~~ db.unicorns.find({}, {name: 1}); ~~~ 默认的,`_id` 字段总是会返回的。我们可以通过这样显式的把它从返回结果中排除 `{name:1, _id: 0}`。 除了 `_id` 字段,你不能把检索和排除混合使用。仔细想想,这是有道理的。你只能显式的检索或者排除某些字段。 ## [](https://github.com/geminiyellow/the-little-mongodb-book/blob/master/zh-cn/mongodb.markdown#排序ordering)排序(Ordering) 到目前位置我已经提到好多次, `find` 返回的是一个游标,它只有在需要的时候才会执行。但是,你在 shell 中看确实到的是 `find` 被立刻执行了。这只是 shell 的行为。 我们可以通过一个 `find` 的链式方法,观察到 `cursors` 的真正行为。我们来看看 `sort`。我们指定我们希望排序的字段,以 JSON 方式,其中 1 表示升序 -1 表示降序。比如: ~~~ //heaviest unicorns first db.unicorns.find().sort({weight: -1}) //by unicorn name then vampire kills: db.unicorns.find().sort({name: 1, vampires: -1}) ~~~ 就像关系型数据库那样,MongoDB 允许对索引进行排序。我们再稍后将详细讨论索引。那,你应该知道的是,MongoDB 对未经索引的字段进行排序是有大小限制的。就是说,如果你试图对一个非常大的没有经过索引的结果集进行排序的话,你会得到个异常。有些人认为这是一个缺点。说实话,我是多希望更多的数据库可以有这种能力去拒绝未经优化的查询。(我不是把每个 MongoDB 的缺点硬说成优点,但是我已经看够了那些缺乏优化的数据库了,我真心希望他们能有一个 strict-mode。) ## [](https://github.com/geminiyellow/the-little-mongodb-book/blob/master/zh-cn/mongodb.markdown#分页paging)分页(Paging) 对结果分页可以通过 `limit` 和 `skip` 游标方法来实现。比如要获取第二和第三重的独角兽,我们可以这样: ~~~ db.unicorns.find() .sort({weight: -1}) .limit(2) .skip(1) ~~~ 通过 `limit` 和 `sort` 的配合,可以在对非索引字段进行排序时避免引起问题。 ## [](https://github.com/geminiyellow/the-little-mongodb-book/blob/master/zh-cn/mongodb.markdown#计数count)计数(Count) shell 中可以直接对一个集合执行 `count` ,像这样: ~~~ db.unicorns.count({vampires: {$gt: 50}}) ~~~ 实际上,`count` 是一个 `cursor` 的方法,shell 只是简单的提供了一个快捷方式。以不提供快捷方式的方法来执行的时候需要这样(在 shell 中同样可以执行): ~~~ db.unicorns.find({vampires: {$gt: 50}}) .count() ~~~ ## [](https://github.com/geminiyellow/the-little-mongodb-book/blob/master/zh-cn/mongodb.markdown#小结-2)小结 使用 `find` 和 `cursors` 非常简单。还讲了一些我们后面章节会用到的或是非常特殊情况才用的命令,不过不管怎样,现在,你应该已经非常熟练使用 mongo shell 以及理解 MongoDB 的基本原则了。
';

第二章 – 更新

最后更新于:2022-04-01 00:54:55

在第一章,我们介绍了 CRUD 的四分之三(create, read, update 和 delete) 操作。这章,我们来专门来讨论我们跳过的那个操作: `update`。 `Update` 有些独特的行为,这是为什么我们把它独立成章。 ## [](https://github.com/geminiyellow/the-little-mongodb-book/blob/master/zh-cn/mongodb.markdown#update-覆盖还是-set)Update: 覆盖还是 $set 最简单的情况, `update` 有两个参数: 选择器 (`where`) 和需要更新字段的内容。假设 Roooooodles 长胖了,你会希望我们这样操作: ~~~ db.unicorns.update({name: 'Roooooodles'}, {weight: 590}) ~~~ (如果你已经把 `unicorns` 集合玩坏了,它已经不是原来的数据了的话,再执行一次 `remove` 删除所有数据,然后重新插入第一章中所有的代码。) 现在,如果你查一下被更新了的记录: ~~~ db.unicorns.find({name: 'Roooooodles'}) ~~~ 你会发现 `update` 的第一个惊喜,没找到任何文档。因为我们指定的第二个参数没有使用任何的更新选项,因此,它**replace** 了原始文档。也就是说, `update` 先根据 `name` 找到一个文档,然后用新文档(第二个参数)覆盖替换了整个文档。这和 SQL 的 `update` 命令的完全不一样。在某些情况下,这非常理想,可以用于某些完全动态更新上。但是,如果你只希望改变一个或者几个字段的值的时候,你应该用 MongoDB 的 `$set` 操作。继续,让我们来更新重置这个丢失的数据: ~~~ db.unicorns.update({weight: 590}, {$set: { name: 'Roooooodles', dob: new Date(1979, 7, 18, 18, 44), loves: ['apple'], gender: 'm', vampires: 99}}) ~~~ 这里不会覆盖新字段 `weight` 因为我们没有指定它。现在让我们来执行: ~~~ db.unicorns.find({name: 'Roooooodles'}) ~~~ 我们拿到了期待的结果。因此,在最开始的时候,我们正确的更新 weight 的方式应该是: ~~~ db.unicorns.update({name: 'Roooooodles'}, {$set: {weight: 590}}) ~~~ ## [](https://github.com/geminiyellow/the-little-mongodb-book/blob/master/zh-cn/mongodb.markdown#update-操作符)Update 操作符 除了 `$set`,我们还可以用其他的更新操作符做些有意思的事情。所有的更新操作都是对字段起作用 - 所以你不用担心整个文档被删掉。比如,`$inc` 可以用来给一个字段增加一个正/负值。假设说 Pilot 获得了非法的两个 vampire kills 点,我们可以这样修正它: ~~~ db.unicorns.update({name: 'Pilot'}, {$inc: {vampires: -2}}) ~~~ 假设 Aurora 忽然长牙了,我们可以给她的 `loves` 字段加一个值,通过 `$push` 操作: ~~~ db.unicorns.update({name: 'Aurora'}, {$push: {loves: 'sugar'}}) ~~~ MongoDB 手册的 [Update Operators](http://docs.mongodb.org/manual/reference/operator/update/#update-operators) 这章,可以查到更多可用的更新操作符的信息。 ## [](https://github.com/geminiyellow/the-little-mongodb-book/blob/master/zh-cn/mongodb.markdown#upserts)Upserts 用 `update` 还有一个最大的惊喜,就是它完全支持 `upserts`。所谓 `upsert` 更新,即在文档中找到匹配值时更新它,无匹配时向文档插入新值,你可以这样理解。要使用 upsert 我们需要向 update 写入第三个参数 `{upsert:true}`。 一个最常见的例子是网站点击计数器。如果我们想保存一个实时点击总数,我们得先看看是否在页面上已经有点击记录,然后基于此再决定执行更新或者插入操作。如果省略 upsert 选项(或者设为 false),执行下面的操作不会带来任何变化: ~~~ db.hits.update({page: 'unicorns'}, {$inc: {hits: 1}}); db.hits.find(); ~~~ 但是,如果我们加上 upsert 选项,结果会大不同: ~~~ db.hits.update({page: 'unicorns'}, {$inc: {hits: 1}}, {upsert:true}); db.hits.find(); ~~~ 由于没有找到字段 `page` 值为 `unicorns`的文档,一个新的文档被生成插入。当我们第二次执行这句命令的时候,这个既存的文档将会被更新,且 `hits` 会被增加到 2。 ~~~ db.hits.update({page: 'unicorns'}, {$inc: {hits: 1}}, {upsert:true}); db.hits.find(); ~~~ ## [](https://github.com/geminiyellow/the-little-mongodb-book/blob/master/zh-cn/mongodb.markdown#批量-updates)批量 Updates 关于 `update` 的最后一个惊喜,默认的,它只更新单个文档。到目前为止,我们的所有例子,看起来都挺符合逻辑的。但是,如果你执行一些像这样的操作的时候: ~~~ db.unicorns.update({}, {$set: {vaccinated: true }}); db.unicorns.find({vaccinated: true}); ~~~ 你肯定会希望,你所有的宝贝独角兽都被接种疫苗了。为了达到这个目的, `multi` 选项需要设为 true: ~~~ db.unicorns.update({}, {$set: {vaccinated: true }}, {multi:true}); db.unicorns.find({vaccinated: true}); ~~~ ## [](https://github.com/geminiyellow/the-little-mongodb-book/blob/master/zh-cn/mongodb.markdown#小结-1)小结 本章中我们介绍了集合的基本 CRUD 操作。我们详细讲解了 `update` 及它的三个有趣的行为。 首先,如果你传 MongoDB 一个文档但是不带更新操作, MongoDB 的 `update` 会默认替换现有文档。因此,你通常要用到 `$set` 操作 (或者其他各种可用的用于修改文档的操作)。 其次, `update` 支持 `upsert` 操作,当你不知道文档是否存在的时候,非常有用。 最后,默认情况下, `update` 只更新第一个匹配文档,因此当你希望更新所有匹配文档时,你要用 `multi` 。
';

第一章 – 基础知识

最后更新于: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`。 多试几次之后,你会发现原来看起来那么格格不入的东西,用起来居然水到渠成。
';

开始

最后更新于:2022-04-01 00:54:51

本书大部分内容将会专注于 MongoDB 的核心功能。我们会用到 MongoDB 的 shell。因为 shell 不但有助于学习,而且还是个很有用的管理工具。实际代码中你需要用到 MongoDB 驱动。 这也引出了关于 MongoDB 你所需要知道的第一件事: 它的驱动。MongoDB 有各种语言的 [官方驱动](http://docs.mongodb.org/ecosystem/drivers/)。这些驱动可以认为是和你所熟悉的各种数据库驱动一样的东西。基于这些驱动,开发社区又创建了更多的语言/框架相关库。比如说,[NoRM](https://github.com/atheken/NoRM) 是一个 C# 语言库,用 LINQ 实现,而 [MongoMapper](https://github.com/jnunemaker/mongomapper) 是一个 Ruby 库,ActiveRecord-friendly。你可以选择直接对 MongoDB 核心进行开发,或选择高级库。之所以要指出,是因为许多新手都觉得迷惑,为什么这里有官方版本和社区版本 - 前者通常关心和 MongoDB 核心的通讯/连接,而后者有更多的语言和框架的实现。 说到这,我希望你可以在 MongoDB 环境中尝试一下我的例子,并且在尝试解决可能遇到的问题。MongoDB 很容易安装和运行,所以让我们花几分钟把所有的东西运行起来。 1. 先打开 [官方下载页面](http://www.mongodb.org/downloads) ,从你选择的操作系统下面的第一行(推荐稳定版本)下载二进制文件。根据开发实际,你可以选择 32位 或者 64位。 2. 解压缩文件 (随便你放哪) 然后进入 `bin` 子目录。现在还不要执行任何命令,只要记住 `mongod` 用来打开服务进程,`mongo` 打开客户端 shell - 大部分时间我们将要使用这两个命令。 3. 在 `bin` 子目录下创建一个文本文件,命名为 `mongodb.config`。 4. 在 mongodb.config 中添加一行: `dbpath=PATH_TO_WHERE_YOU_WANT_TO_STORE_YOUR_DATABASE_FILES`。比如,在 Windows 你可以写 `dbpath=c:\mongodb\data` ,在 Linux 可能是 `dbpath=/var/lib/mongodb/data`。 5. 确保你指定的 `dbpath` 确实存在。 6. 执行 mongod ,带上参数 `--config /path/to/your/mongodb.config` 。 以 Windows 用户为例,如果你解压下载文档到 `c:\mongodb\` ,并且你创建了 `c:\mongodb\data\` ,那么在`c:\mongodb\bin\mongodb.config` 你要指定 `dbpath=c:\mongodb\data\`。 然后你可以在 CMD 执行 `mongod` 如下命令行`c:\mongodb\bin\mongod --config c:\mongodb\bin\mongodb.config`。 为省心你可以把 `bin` 文件夹路径添加到环境变量 PATH 中,可以简化命令。MacOSX 和 Linux 用户方法几乎一样。唯一需要改变的是路径。 希望你现在已经可以启动 MongoDB 了。如果出现异常,仔细阅读一下异常信息 - 服务器对异常的解释做得非常好。 现在你可以执行 `mongo` (没有 _d_) ,链接 shell 到你的服务器上了。尝试输入 `db.version()` 来确认所有都正确执行了。你应该能拿到一个已安装的版本号。
';

简介

最后更新于:2022-04-01 00:54:49

> 这章那么短不是我的错,MongoDB 就真的很易学。 都说技术在飞速发展。确实,有接连不断的新技术新方法出现。但是,我一直认为,程序员用到的基础技术的发展却是相当缓慢的。你可以好几年不学习但还能混得下去。令人惊讶的其实是成熟技术的被替换速度。就像在一夜之间,那些长期稳定成熟的技术发现它们不再被开发者关注。 最好的例子就是 NoSQL 技术的发展,以及它对稳定的关系型数据库市场的蚕食。看起来就像,昨天网络还是由 RDBMS 们来驱动的,而今天,就冒出五种左右的 NoSQL 解决案已经证明了它们都是值得拥有的。 虽然这些转变看起来都是一夜之间发生的,实际上他们他们可能花了数年的时间来取得公众的认可。最开始是由一小波开发者和公司在推动。解决方案被不断细化,吸取教训,然后一个新技术就这样诞生了,慢慢的后来者也开始了尝试。再次重申,NoSQL 的许多解决方案并不是为了取代传统的存储方案,而是解决一些特殊需求,填补了传统解决方案的一些空白。 说了那么多,我们第一件应该解决的事情是解释一下什么是 NoSQL。它是一个宽松的概念,不同的人有不同的见解。就个人而言,我通常认为它是数据存储系统的一部分。换而言之,NoSQL (重申, 就我而言),的好处是让你的持久层不需要一个独立的系统。历史上,传统的关系数据库厂商尝试把他们的产品当作一揽子解决方案,NoSQL 倾向于扮演,在特定的工作中充当最好的工具这种角色。因此,你的 NoSQL 架构中还是可以用到关系型数据库,比如说 MySQL,但是可以也可以用 Redis 作为系统中某部分的持久层,或者是用到 Hadoop 来处理大数据。简而言之,NoSQL 就是需要用开放的可代替的意识,使用现有的或者未来的方式和工具来管理你的数据。 你会想知道,MongoDB 是不是适用于这一切。作为一个面向文档数据库,MongoDB 是最通用的 NoSQL 解决案。它可以看成是关系型数据库的代替方案。和关系型数据库一样,它也可以和其他的 NoSQL 解决案搭配在一起更好的工作。MongoDB 有优点也有缺点,我们将会在本书后面的章节中介绍。 如你所见,我们混用了 MongoDB 和 Mongo 两个术语。
';

关于

最后更新于:2022-04-01 00:54:46

![2015-06-22/55877e414c7fd](https://docs.gechiui.com/gc-content/uploads/sites/kancloud/2015-06-22_55877e414c7fd.png) ## 许可 本书《 The Little MongoDB Book 》基于 Attribution-NonCommercial 3.0 Unported license. **你无须为本书付款。** 你可以自由的复制,分发,修改和传阅本书。但请认可该书属于作者 Karl Seguin,并请勿将本书用于任何商业目的。 你可以在以下链接查看完整的许可文档: [http://creativecommons.org/licenses/by-nc/3.0/legalcode](http://creativecommons.org/licenses/by-nc/3.0/legalcode) ## [](https://github.com/geminiyellow/the-little-mongodb-book/blob/master/zh-cn/mongodb.markdown#关于作者)关于作者 Karl Seguin 在多领域有着丰富经验,他是 .NET 和 Ruby 的开发专家。他也参与贡献 OSS 项目, 还是技术文档撰写人而且偶尔做做演讲。MongoDB 方面,他是 C# MongoDB 库 NoRM 的核心开发者,写有互动入门教程 [mongly](http://openmymind.net/mongly/) 和 [Mongo Web Admin](https://github.com/karlseguin/Mongo-Web-Admin)。他用 MongoDB,为休闲游戏开发者写了一个免费服务, [mogade.com](http://mogade.com/)。 Karl 还编写了 [The Little Redis Book](http://openmymind.net/2012/1/23/The-Little-Redis-Book/) _1_ 你可以在 [http://openmymind.net](http://openmymind.net/) 找到他的 Blog,或者通过 [@karlseguin](http://twitter.com/karlseguin) 在 Twitter 上关注他。 ## [](https://github.com/geminiyellow/the-little-mongodb-book/blob/master/zh-cn/mongodb.markdown#鸣谢)鸣谢 特别感谢 [Perry Neal](http://twitter.com/perryneal), 赐予我你的视野,精神,和热情。你赐予了我无尽的力量。感恩。 ## [](https://github.com/geminiyellow/the-little-mongodb-book/blob/master/zh-cn/mongodb.markdown#最新版本)最新版本 最新的版本由 Asya Kamsky 更新到了 MongoDB 2.6 。本书最新代码可以在这里获得: [http://github.com/karlseguin/the-little-mongodb-book](http://github.com/karlseguin/the-little-mongodb-book). ### [](https://github.com/geminiyellow/the-little-mongodb-book/blob/master/zh-cn/mongodb.markdown#中文版本)中文版本 Karl 在 [the-little-mongodb-book](https://github.com/karlseguin/the-little-mongodb-book) 的 Github 链接中给出了 [justinyhuang](https://github.com/justinyhuang) 的 [the-little-mongodb-book-cn](https://github.com/justinyhuang/the-little-mongodb-book-cn) 链接。但貌似 justinyhuang 并没有同步更新到 MongoDB 2.6 。内容上也和原文稍微有点出入,并且由于本人水平有限,无法提交自信正确的内容。因此重开一项目。如果你被搜索引擎引导到本工程,在此向你致歉,并希望有能力者且有时间者一同完善和同步本工程。你可以通过我的 邮箱 [geminiyellow@gmail.com](mailto:geminiyellow@gmail.com) 来联系我,或者通过 [@geminiyellow](https://twitter.com/geminiyellow) 在 Twitter 上关注我。 最新中文版本基于 [asya999](https://github.com/asya999) 在 May 29, 2014 提交的 [#38](https://github.com/karlseguin/the-little-mongodb-book/pull/38) SHA 是:6d4dce8ead6a767e1e8de1b59f714510d36d366f
';