MongoDB-实践教程-一-

114 阅读1小时+

MongoDB 实践教程(一)

原文:Practical MongoDB

协议:CC BY-NC-SA 4.0

一、大数据

"Big data is a term used to describe data with massive data, various structures and high-speed generation. This kind of data challenges the traditional RDBMS system for storing and processing data. Bidding data paves the way for new methods of processing and storing data. "

在本章中,我们将讨论大数据的基础知识、来源和挑战。我们将向您介绍大数据的三个 v(容量、速度和多样性),以及传统技术在处理大数据时面临的限制。

1.1 入门

大数据以及云、社交、分析和移动性是当今信息技术领域的热门词汇。大众对互联网和电子设备的可用性与日俱增。具体而言,智能手机、社交网站和平板电脑和传感器等其他数据生成设备正在创造数据爆炸。数据是从各种来源以各种格式生成的,如视频、文本、语音、日志文件和图像。一秒钟的高清视频产生的字节数是一页文本的 2000 倍。

考虑一下脸书公司网站上报道的以下统计数据:

There were 968 million daily active users on average for June of 2015. There were 844 million mobile daily active users on average for June of 2015.   There were 1.49 billion monthly active users as of June 30, 2015. There were 1.31 billion mobile monthly active users as of June 30, 2015.   There were 4.5 billion likes generated daily as of May 2013, which is a 67 percent increase from August 2012.  

1-1 描绘了 Twitter 的统计数据。

A332296_1_En_1_Fig1_HTML.jpg

图 1-1。

If you printed Twitter…

这里有另一个例子:考虑像去看电影这样的简单事件可以生成的数据量。你首先在电影评论网站上搜索一部电影,阅读关于这部电影的评论,并提出疑问。你可以在推特上谈论这部电影,或者在脸书上发布去看电影的照片。在去剧院的途中,您的 GPS 系统会跟踪您的路线并生成数据。

你可以想象一下:智能手机、社交网站和其他媒体正在产生大量数据,供公司处理和存储。当数据的规模对典型软件工具捕获、处理、存储和管理数据的能力构成挑战时,我们就有了大数据。图 1-2 图形化定义了大数据。

A332296_1_En_1_Fig2_HTML.jpg

图 1-2。

Definition of Big Data

1.2 大数据

大数据是指具有高容量、高速生成、多品种的数据。我们来看几个大数据的事实和数字。

1.2.1 关于大数据的事实

世界各地的各种研究团队已经对生成的数据量进行了分析。例如,IDC 的分析显示,仅一年(2007 年)产生的数字数据量就超过了全球的总存储容量,这意味着无法存储所有产生的数据。此外,数据生成的速度将很快超过数据存储容量的增长速度。

以下章节涵盖了 2011 年 5 月发布的 MGI(麦肯锡全球研究院)报告( www.mckinsey.com/insights/business_technology/big_data_the_next_frontier_for_innovation)中的观点。这项研究表明,大数据的商业和经济可能性及其更广泛的影响是商业领袖和政策制定者必须解决的重要问题。that)

1.2.1.1 大数据的规模因行业而异

大数据的增长是每个行业都存在的现象。MGI 估计,2010 年,全球企业使用了超过 7eb 的增量磁盘驱动器数据存储容量;有趣的是,其中近 80%的数据似乎是存储在其他地方的重复数据。MGI 还估计,到 2009 年,美国经济中几乎所有行业的每家公司平均至少存储 200 TB 的数据,许多行业的每家公司平均存储数据超过 1pb。

一些部门的数据密集程度远远高于其他部门;在这种情况下,数据密集度是指该行业的公司/企业积累的平均数据量,这意味着它们更有潜力从大数据中获取价值。

金融服务部门,包括银行、投资和证券服务,高度面向事务;法规还要求它们存储数据。分析表明,平均每个公司存储的数字数据最多。

通信和媒体公司、公用事业和政府也有每个企业或组织存储的大量数字数据,这似乎反映了这样的事实,即这些实体具有大量的操作和多媒体数据。

离散制造业和流程制造业拥有最高的总数据存储字节数。然而,这些部门的密集程度要低得多,因为它们分散在许多公司中。

1.2.1.2 大数据类型因行业而异

MGI 的研究还表明,存储的数据类型也因行业而异。例如,零售和批发、政府行政部门以及金融服务都会产生大量的文本和数字数据,包括客户数据、事务信息以及数学建模和模拟。制造业、医疗保健、媒体和通信等行业负责更高百分比的多媒体数据。X 射线、CT 和其他扫描形式的图像数据在医疗保健的数据存储量中占主导地位。

就大数据的地理分布而言,北美和欧洲目前拥有全球总量的 70%。由于云计算,一个地区产生的数据可以存储在另一个国家的数据中心。因此,拥有大量云和托管提供商产品的国家往往拥有大量数据存储。

1.3 大数据源

在本节中,我们将讨论导致数据不断增长的主要因素。图 1-3 描述了主要来源。

A332296_1_En_1_Fig3_HTML.jpg

图 1-3。

Sources of data

正如 MGI 报告所强调的,这一数据的主要来源是

  • 企业现在以更精细的方式收集数据,为每笔事务附加更多细节,以了解消费者行为。
  • 医疗保健、产品公司等行业中多媒体使用的增加。
  • 脸书、推特等社交媒体网站越来越受欢迎。
  • 智能手机的快速普及,使用户能够积极使用社交媒体网站和其他互联网应用。
  • 在日常生活中,通过网络连接到计算资源的传感器和设备的使用越来越多。

MGI 报告还预测,未来五年,传感器等机器对机器设备(也被称为物联网,或物联网)的数量将以每年超过 30%的速度增长。

因此,数据的增长率和多样性都在增加。此外,数据生成的模式已经从少数公司生成数据,其他公司消费数据,转变为人人生成数据,人人消费数据。这是由于消费 IT 和互联网技术的渗透以及社交媒体等趋势。图 1-4 描述了数据生成模型的变化。

A332296_1_En_1_Fig4_HTML.jpg

图 1-4。

Data model

1.4 大数据的三个 v

我们将大数据定义为具有三个 v 的数据:量、速度和多样性,如图 1-5 所示。让我们来看看这三个方面。组织和 It 领导者必须关注这些方面。

A332296_1_En_1_Fig5_HTML.gif

图 1-5。

The three Vs of big data. The “big” isn’t just the volume

1.4.1 体积

大数据中的体积意味着数据的大小。如前所述,各种因素影响着大数据的规模:随着业务变得越来越以事务为导向,我们看到事务数量不断增加;越来越多的设备连接到互联网,这增加了容量;互联网的使用越来越多;内容的数字化也在增加。图 1-6 描绘了自 2009 年以来数字世界的增长。

A332296_1_En_1_Fig6_HTML.jpg

图 1-6。

Digital universe size

在今天的场景中,数据不仅仅是从企业内部产生的;它也是基于与扩展企业和客户的事务而生成的。这需要企业对客户数据进行大量维护。如今,十亿字节的规模变得越来越普遍。图 1-7 描绘了数据增长率。

A332296_1_En_1_Fig7_HTML.jpg

图 1-7。

Growth rate

如此庞大的数据量是大数据技术面临的最大挑战。以及时且经济的方式存储、处理和访问数据所需的存储和处理能力是巨大的。

1.4.2 品种

从各种设备和来源生成的数据没有固定的格式或结构。与文本相比,CSV 或 RDBMS 数据不同于文本文件、日志文件、流视频、照片、仪表读数、股票行情数据、pdf、音频和各种其他非结构化格式。

如今,数据的结构无法控制。新的数据源和数据结构正在快速创建。因此,技术上的责任是找到一种解决方案来分析和可视化存在的大量数据。例如,要为通勤者提供备用路线,交通分析应用需要来自数百万部智能手机和传感器的数据,以提供对交通状况和备用路线的准确分析。

1.4.3 速度

大数据中的速度是数据创建的速度和处理数据的速度。如果数据不能以要求的速度处理,它就失去了意义。由于来自社交媒体网站、传感器、报价器、计量和监控的数据流,无论数据是动态的还是静态的,组织都必须快速处理数据(参见图 1-8 )。对于大数据技术来说,以足够快的速度应对和处理数据是另一个挑战。

A332296_1_En_1_Fig8_HTML.jpg

图 1-8。

The three aspects of data

在许多大数据用例中,实时洞察至关重要。例如,算法事务系统从市场和 Twitter 等社交媒体网站获取实时信息,以做出股票事务决策。处理这些数据的任何延迟都可能意味着损失数百万美元的股票事务机会。

每当讨论大数据时,都会谈到第四个 V。第四个 V 是准确性,这意味着并不是所有的数据都是重要的,所以确定什么将提供有意义的洞察力,什么应该被忽略是至关重要的。

1.5 大数据的使用

本节将重点介绍使用大数据为组织创造价值的方法。在我们深入研究如何让大数据为组织所用之前,我们先来看看大数据为什么重要。

大数据是一个全新的数据来源;它是当你在博客上发表文章时产生的数据,比如一个产品或旅行。以前,这种细微的可用信息没有被捕获。现在,it 和采用此类数据的组织可以追求创新、提高灵活性并增加盈利能力。

大数据可以以多种方式为任何组织创造价值。正如 MGI 报告中所列,这可以大致分为五种使用大数据的方式。

能见度

相关利益相关者及时访问数据会产生巨大的价值。我们用一个例子来理解这个。假设一家制造公司的 R&D、工程和制造部门分散在不同的地理位置。如果这些数据可以在所有这些部门之间访问,并且可以很容易地集成,它不仅可以减少搜索和处理时间,而且还有助于根据当前的需要提高产品质量。

1.5.2 发现和分析信息

大数据的大部分价值来自于从外部来源收集的数据可以与组织的内部数据合并。组织正在获取关于库存、员工和客户的详细数据。使用所有这些数据,他们可以发现和分析新的信息和模式;因此,这些信息和知识可以用来改进过程和性能。

细分和定制

大数据使组织能够创建量身定制的产品和服务,以满足特定细分市场的需求。这也可用于社会部门,以准确划分人口,并针对具体需求制定福利计划。基于各种参数的客户细分有助于有针对性的营销活动和定制产品以满足客户的需求。

辅助决策

大数据可以极大地降低风险,改善决策,并揭示有价值的见解。信用卡处理中的自动化欺诈警报系统和库存的自动微调是基于大数据分析帮助或自动化决策的系统示例。

创新

大数据以产品和服务的形式实现了新思想的创新。它可以在现有的基础上进行创新,从而接触到更多的人。使用为实际产品收集的数据,制造商不仅可以创新以创造下一代产品,还可以创新销售产品。

例如,可以分析来自机器和车辆的实时数据,以提供对维护计划的洞察;可以监控机器的磨损,以制造更有弹性的机器;油耗监控可以提高发动机的效率。实时交通信息已经为通勤者提供了选择替代路线的选项,让他们的生活变得更加轻松。

因此,大数据不仅仅是数据量。这是从不断增加的数据中发现有意义的见解的机会。它帮助组织做出更明智的决策,使他们更加敏捷。它不仅为组织提供了通过做出明智的决策来加强现有业务的机会,还帮助识别新的机会。

1.6 大数据挑战

大数据也带来了一些挑战。在本节中,我们将重点介绍其中的几个。

政策和程序

随着越来越多的数据在全球范围内被收集、数字化和移动,策略和法规遵从性问题变得越来越重要。数据隐私、安全、知识产权和保护对组织来说非常重要。

遵守各种法令和法律要求给数据处理带来了挑战。围绕数据的所有权和责任问题是大数据案例中需要处理的重要法律问题。

此外,许多大数据项目利用公共云计算提供商的可扩展性功能。这给合规性带来了挑战。

还需要回答关于谁拥有数据、什么被定义为数据的合理使用以及谁对数据的准确性和保密性负责的政策问题。

数据的获取

访问供消费的数据是大数据项目面临的一项挑战。有些数据可能会被第三方获取,获取这些数据可能会面临法律和合同方面的挑战。

关于产品或服务的数据可以在脸书、Twitter feeds、评论和博客上获得,那么产品所有者如何从不同提供商拥有的不同来源访问这些数据呢?

同样,需要将访问大数据的合同条款和经济激励捆绑在一起,以使消费者能够获得数据。

技术和工艺

必须利用专门为满足大数据需求而构建的新工具和技术,而不是试图通过遗留系统来解决上述问题。一方面,遗留系统在处理大数据方面的不足,另一方面,新技术中缺乏经验丰富的资源,这是任何大数据项目都必须应对的挑战。

1.7 遗留系统和大数据

在本节中,我们将讨论组织在使用传统系统管理大数据时面临的挑战。

1.7.1 大数据结构

遗留系统设计用于处理结构化数据,其中定义了带有列的表。列中保存的数据的格式也是已知的。

但是,大数据是有很多结构的数据。基本上是图像、视频、日志等非结构化数据。

由于大数据可能是非结构化的,为通过基于不同列中保存的特定数据类型的索引等技术来执行快速查询和分析而创建的遗留系统无法用于保存或处理大数据。

数据存储

传统系统使用大型服务器、NAS 和 SAN 系统来存储数据。随着数据的增加,服务器大小和后端存储大小也必须增加。传统的传统系统通常在纵向扩展模式下工作,需要向服务器添加越来越多的计算、内存和存储来满足不断增长的数据需求。因此,处理时间呈索引级增长,这破坏了大数据的另一个重要要求,即速度。

数据处理

遗留系统中的算法设计用于处理结构化数据,如字符串和整数。它们也受到数据大小的限制。因此,遗留系统无法处理非结构化数据、海量此类数据以及需要执行处理的速度。

因此,为了从大数据中获取价值,我们需要在存储、计算和检索领域部署更新的技术,并且我们需要分析数据的新技术。

1.8 大数据技术

你见过什么是大数据。在这一节中,我们将简要地看一下哪些技术可以处理这个庞大的数据源。讨论中的技术需要有效地接受和处理不同类型的数据。

使组织能够充分利用大数据的最新技术进步如下:

New storage and processing technologies designed specifically for large unstructured data   Parallel processing   Clustering   Large grid environments   High connectivity and high throughput   Cloud computing and scale-out architectures  

越来越多的技术正在利用这些技术进步。在本书中,我们将讨论 MongoDB,这是一种可用于存储和处理大数据的技术。

1.9 摘要

在本章中,您了解了大数据。您研究了产生大数据的各种来源,以及大数据的用途和带来的挑战。您还了解了为什么需要更新的技术来存储和处理大数据。

在接下来的章节中,您将了解一些有助于组织管理大数据并使他们能够从大数据中获得有意义的见解的技术。

二、NoSQL

“NoSQL 是一种设计互联网规模的数据库解决方案的新方法。它不是一种产品或技术,而是一个术语,它定义了一组不是基于传统 RDBMS 原则的数据库技术。”

在这一章,我们将涵盖 NoSQL 的定义和基础知识。我们将向你介绍 CAP 定理,并讨论 NRW 符号。我们将比较 ACID 和 BASE 方法,并通过比较 NoSQL 和 SQL 数据库技术来结束本章。

2.1 SQL

RDBMS 的想法来自 e . f . Codd 1970 年的白皮书《大型共享数据库的关系数据模型》用于查询 RDBMS 系统的语言是 SQL (Sequel 查询语言)。

RDBMS 系统非常适合存储在列和行中的结构化数据,可以使用 SQL 查询这些数据。RDBMS 系统基于 ACID 事务的概念。ACID 代表原子性、一致性、孤立性和持久性,其中

  • 原子意味着要么完全应用事务的所有更改,要么根本不应用。
  • 一致意味着数据在应用事务后处于一致状态。这意味着提交事务后,获取特定数据的查询将看到相同的结果。
  • 隔离意味着应用于同一组数据的事务相互独立。因此,一个事务不会干扰另一个事务。
  • 持久意味着更改在系统中是永久性的,即使出现任何故障也不会丢失。

2.2 NoSQL

NoSQL 是一个用来指非关系数据库的术语。因此,它包含了大多数不基于传统 RDBMS 原则的数据存储,并用于处理 Internet 规模的大型数据集。

正如上一章所讨论的,大数据对传统的数据存储和处理方式(如 RDBMS 系统)提出了挑战。因此,我们看到了 NoSQL 数据库的兴起,这种数据库的设计是为了在时间和成本的限制下处理如此巨大数量和种类的数据。

因此,NoSQL 数据库是从处理大数据的需求发展而来的;传统的 RDBMS 技术不能提供足够的解决方案。图 2-1 显示了与结构化数据相比,这些年来非/半结构化数据的增长。

A332296_1_En_2_Fig1_HTML.jpg

图 2-1。

Structured vs. un/Semi-Structured data

下面是一些非常适合 NoSQL 数据库的大数据用例示例:

  • 社交网络图:谁和谁有联系?谁的帖子应该出现在社交网站的用户墙或主页上?
  • 搜索和检索:用特定的关键字搜索所有相关的页面,按关键字在页面上出现的次数排序。

2.2.1 定义

NoSQL 没有正式的定义。它代表了一种与 RDBMS 根本不同的持久性/数据存储机制。但是如果硬要定义 NoSQL,这里就是:NoSQL 是不遵循 RDBMS 原则的数据存储的总称。

Note

该术语最初用于表示“如果您想要伸缩,就不要使用 SQL”后来,这被重新定义为“不仅仅是 SQL”,这意味着除了 SQL 之外,还存在其他补充的数据库解决方案。

2 . 2 . 2 NoSQL 简史

1998 年,Carlo Strozzi 创造了术语 NoSQL。他用这个术语来标识他的数据库,因为数据库没有 SQL 接口。这个术语在 2009 年初再次出现,当时 Eric Evans(Rackspace 的一名员工)在一次关于开源分布式数据库的活动中使用这个术语来指代非关系型的分布式数据库,并且没有遵循关系型数据库的 ACID 特性。

2.3 酸与碱

在介绍中,我们提到了传统的 RDBMS 应用关注于 ACID 事务。无论这些品质看起来多么重要,它们都与 Web 规模的应用的可用性和性能要求不相容。

比方说,你有一家像 OLX 这样的公司,销售诸如未使用的家庭用品(旧家具、车辆等)之类的产品。)并使用 RDBMS 作为其数据库。让我们考虑两种情况。

第一个场景:让我们看一个电子商务购物网站,用户正在购买产品。在事务过程中,用户锁定数据库的一部分,即库存,其他用户必须等待,直到锁定的用户完成事务。

第二种情况:应用可能最终使用缓存的数据,甚至未锁定的记录,导致不一致。在这种情况下,当库存实际为零时,两个用户可能最终购买了该产品。

系统可能会变慢,影响可扩展性和用户体验。

与传统 RDBMS 系统的 ACID 方法相反,NoSQL 使用一种通常称为 BASE 的方法来解决这个问题。在解释 BASE 之前,我们先来探讨一下 CAP 定理的概念。

2.3.1 上限定理(布鲁尔定理)

埃里克·布鲁尔在 2000 年概述了上限定理。这是一个重要的概念,需要处理分布式数据库的开发者和架构师很好地理解。该定理指出,在分布式环境中设计应用时,存在三个基本要求,即一致性、可用性和分区容差。

  • 一致性意味着在执行任何更改数据的操作后,数据保持一致,并且访问应用的所有用户或客户端看到相同的更新数据。
  • 可用性意味着系统始终可用。
  • 分区容差意味着,即使系统被划分为无法相互通信的服务器组,系统也将继续运行。

CAP 定理指出,在任何时间点,分布式系统只能满足上述三个保证中的两个(图 2-2 )。

A332296_1_En_2_Fig2_HTML.jpg

图 2-2。

CAP Theorem

基地

埃里克·布鲁尔创造了基本的首字母缩写词。基础可以解释为

  • 基本可用意味着系统在 CAP 定理中是可用的。
  • 软状态表示即使没有输入提供给系统,状态也会随着时间而改变。这符合最终的一致性。
  • 最终一致性意味着系统将在长期内达到一致性,前提是在此期间没有输入被发送到系统。

因此,BASE 与 RDBMS ACID 事务相反。

您已经看到 NoSQL 数据库最终是一致的,但是不同的 NoSQL 数据库最终的一致性实现可能会有所不同。

NRW 是用于描述最终一致性模型如何在 NoSQL 数据库中实现的符号,其中

  • n 是数据库维护的数据副本的数量。
  • r 是应用在返回读取请求的输出之前需要引用的副本数。
  • w 是在将写操作标记为成功完成之前需要写入的数据副本的数量。

使用这些符号配置,数据库实现了最终一致性的模型。

可以在读取和写入操作级别实现一致性。

  • 写操作
  • N=W 意味着在将控制权返回给客户端并将写操作标记为成功之前,写操作将更新所有数据副本。这类似于传统 RDBMS 数据库在实现同步复制时的工作方式。此设置将降低写入性能。
  • 如果写入性能是一个问题,这意味着您希望快速写入,您可以设置 W=1,R=N。这意味着写入将只更新任何一个拷贝,并将写入标记为成功,但每当用户发出读取请求时,它将读取所有拷贝以返回结果。如果任一拷贝未更新,它将确保更新相同的拷贝,然后只有读取会成功。这种实现会降低读取性能。
  • 因此,大多数 NoSQL 实现使用 N>W>1。这意味着需要成功更新不止一个节点;然而,并非所有节点都需要同时更新。
  • 读取操作
  • 如果 R 设置为 1,读取操作将读取任何可能过时的数据副本。如果 R>1,则读取多个副本,并将读取最近的值。但是,这可能会降低读取操作的速度。
  • 使用 N

2-1 比较了酸和碱。

表 2-1。

ACID vs. BASE

| 酸 | 基础 | | --- | --- | | 原子数 | 基本可用 | | 一致性 | 最终一致性 | | 隔离 | 柔软状态 | | 持久耐用 |   |

2.4 NoSQL 的优势和劣势

在这一节中,您将看到 NoSQL 数据库的优点和缺点。

2 . 4 . 1 NoSQL 的优势

让我们谈谈 NoSQL 数据库的优势。

  • 高可伸缩性:当事务率和快速响应需求增加时,这种向上扩展的方法就会失败。与此相反,新一代 NoSQL 数据库旨在向外扩展(即使用低端商用服务器进行水平扩展)。
  • 可管理性和管理性:NoSQL 数据库主要用于自动修复、分布式数据和更简单的数据模型,导致可管理性和管理性较低。
  • 低成本:NoSQL 数据库通常被设计成与廉价的商用服务器集群一起工作,使用户能够以较低的成本存储和处理更多的数据。
  • 灵活的数据模型:NoSQL 数据库有一个非常灵活的数据模型,使它们能够处理任何类型的数据;它们不符合严格的 RDBMS 数据模型。因此,任何涉及更新数据库模式的应用更改都可以轻松实现。

NoSQL 的缺点

除了上面提到的优势之外,在开始使用这些平台开发应用之前,您还需要了解许多障碍。

  • 成熟度:大多数 NoSQL 数据库都是预生产版本,其关键特性仍有待实现。因此,在决定使用 NoSQL 数据库时,您应该对产品进行适当的分析,以确保这些特性被完全实现,而不是仍然在待办事项列表中。
  • 支持:支持是你需要考虑的一个限制。大多数 NoSQL 数据库来自开源的初创企业。因此,与企业软件公司相比,支持是非常少的,并且可能没有全球影响力或支持资源。
  • 有限的查询能力:由于 NoSQL 数据库通常是为了满足 web 级应用的伸缩需求而开发的,所以它们提供的查询能力有限。一个简单的查询需求可能涉及大量的编程专业知识。
  • 管理:尽管 NoSQL 旨在提供一个无管理的解决方案,但它仍然需要技巧和精力来安装和维护该解决方案。
  • 专业知识:由于 NoSQL 是一个不断发展的地区,开发者和管理员社区对该技术的专业知识非常有限。

虽然 NoSQL 正在成为数据库领域的重要组成部分,但是您需要了解这些产品的局限性和优势,以便正确选择 NoSQL 数据库平台。

2.5 SQL 与 NoSQL 数据库

现在您已经了解了关于 NoSQL 数据库的细节。尽管 NoSQL 越来越多地被用作数据库解决方案,但它并不是要取代 SQL 或 RDBMS 数据库。在这一节中,您将看到 SQL 和 NoSQL 数据库之间的差异。

让我们快速回顾一下 RDBMS 系统。RDBMS 系统已经流行了大约 30 年,甚至现在它们还是应用数据存储解决方案架构师的默认选择。如果我们要列出 RDBMS 系统的几个优点,首先也是最重要的是 SQL 的使用,它是一种用于数据处理的丰富的声明式查询语言。很好的被用户理解。此外,RDBMS 系统提供了对事务的 ACID 支持,这在许多领域是必不可少的,例如银行应用。

然而,RDBMS 系统的最大缺点是,随着数据的增加,它很难处理模式变化和伸缩问题。随着数据的增加,读/写性能会下降。您面临 RDBMS 系统的伸缩问题,因为它们主要是为纵向扩展而不是横向扩展而设计的。

与 SQL RDBMS 数据库相反,NoSQL 提倡脱离 RDBMS 范式的数据存储。

让我们讨论一下技术场景,以及它们在 RDBMS 和 NoSQL 中的比较:

  • 模式灵活性:这对于将来的增强和与外部应用(出站或入站)的集成是必不可少的。RDBMS 在设计上相当不灵活。添加列是绝对不允许的,尤其是当表中有一些数据时。原因包括默认值、索引和性能影响。通常,您最终会创建新的表,并通过引入跨表的关系来增加复杂性。
  • 复杂的查询:传统的表设计导致开发者编写复杂的连接查询,这不仅难以实现和维护,而且需要大量的数据库资源来执行。
  • 数据更新:跨表更新数据可能是比较复杂的场景之一,尤其是当它们是事务的一部分时。请注意,长时间保持事务打开会影响性能。您还必须计划将更新传播到系统中的多个节点。如果系统不支持多个主机或同时写入多个节点,则存在节点故障和整个应用进入只读模式的风险。
  • 可伸缩性:通常唯一需要的可伸缩性是读操作。但是,随着运营的增长,有几个因素会影响这一速度。要问的一些关键问题是:基于 NoSQL 的解决方案为上面列出的大多数挑战提供了答案。现在让我们来看看 NoSQL 对上面提到的每个技术问题提供了什么。
    • 跨物理数据库实例同步数据需要多长时间?
    • 跨数据中心同步数据需要多长时间?
    • 同步数据的带宽要求是什么?
    • 交换的数据优化了吗?
    • 跨服务器同步任何更新时的延迟是多少?通常,记录会在更新期间被锁定。NoSQL-based solutions provide answers to most of the challenges listed above.Let’s now see what NoSQL has to offer against each technical question mentioned above.
  • 模式灵活性:面向列的数据库将数据存储为列,而不是 RDBMS 中的行。这允许根据需要动态地添加一个或多个列。类似地,允许存储半结构化数据的文档存储也是不错的选择。
  • 复杂查询:NoSQL 数据库不支持关系或外键。没有复杂的查询。没有联接语句。这是缺点吗?如何跨表查询?毫无疑问,这是一个功能上的缺陷。要跨表查询,必须执行多个查询。数据库是一种共享资源,跨应用服务器使用,不能尽快停止使用。这些选项包括简化要执行的查询、缓存数据和在应用层执行复杂操作的组合。许多数据库都提供内置的实体级缓存。这意味着当一个记录被访问时,它可以被数据库自动透明地缓存。为了性能和规模,缓存可以是内存中分布式缓存。
  • 数据更新:跨物理实例的数据更新和同步是很难解决的工程问题。与跨多个数据中心同步相比,数据中心内节点间的同步有一组不同的要求。人们希望延迟最好在几毫秒或几十毫秒之内。NoSQL 解决方案提供了很好的同步选项。例如,MongoDB 允许跨节点的并发更新、带冲突解决的同步,以及最终在几毫秒内运行的可接受时间内跨数据中心的一致性。因此,MongoDB 没有隔离的概念。注意,现在因为管理事务的复杂性可能从数据库中移出,应用将不得不做一些艰苦的工作。这方面的一个例子是在实现事务时的两阶段提交( http://docs.mongodb.org/manual/tutorial/perform-two-phase-commits/ )。大量数据库提供多版本并发控制(MCC)来实现事务一致性。正如易贝大学的技术研究员丹·普里切特所说,eBay.com 不使用事务。注意,PayPal 确实使用事务。
  • 可伸缩性:NoSQL 解决方案提供了更好的可伸缩性,原因显而易见。面向事务的 RDBMS 所需的许多复杂性在不符合 ACID 的 NoSQL 数据库中并不存在。有趣的是,因为 NoSQL 不提供跨表引用,也不可能有连接查询,而且因为您不能编写一个查询来比较多个表中的数据,所以一个简单而合理的解决方案是——有时——跨表复制数据。在某些情况下,将信息嵌入到主实体中——尤其是在一对一映射的情况下——可能是个好主意。

2-2 比较了 SQL 和 NoSQL 技术。

表 2-2。

SQL vs. NoSQL

|   | SQL 数据库 | NoSQL 数据库 | | --- | --- | --- | | 类型 | 所有类型都支持 SQL 标准。 | 存在多种类型,如文档存储、键值存储、列数据库等。 | | 发展历史 | 开发于 1970 年。 | 开发于 2000 年代。 | | 例子 | SQL Server、Oracle、MySQL。 | 蒙戈布,巴塞,卡珊德拉。 | | 数据存储模型 | 数据存储在表的行和列中,其中每一列都有特定的类型。这些表格通常是根据标准化原则创建的。联接用于从多个表中检索数据。 | 数据模型取决于数据库类型。比方说,数据存储为键值存储的键值对。在基于文档的数据库中,数据存储为文档。与 RDBMS 的僵硬的表模型相比,数据模型是灵活的。 | | 计划 | 固定的结构和模式,因此对模式的任何更改都会涉及到数据库的更改。 | 动态模式、新的数据类型或结构可以通过扩展或改变当前模式来适应。可以动态添加新字段。 | | 可量测性 | 使用放大方法;这意味着随着负载的增加,需要购买更大、更贵的服务器来容纳数据。 | 使用横向扩展方法;这意味着将数据负载分布在廉价的商用服务器上。 | | 支持事务 | 支持 ACID 和事务。 | 支持分区和可用性,以及对事务的妥协。事务存在于特定的级别,例如数据库级别或文档级别。 | | 一致性 | 一致性强。 | 取决于产品。很少有人选择提供强一致性,而很少有人提供最终一致性。 | | 支持 | 提供高水平的企业支持。 | 开源模式。通过构建开源产品的第三方或公司提供支持。 | | 成熟 | 已经存在很久了。 | 有的是成熟的;其他的在进化。 | | 查询功能 | 通过易于使用的 GUI 界面提供。 | 查询可能需要编程技能和知识。而不是一个用户界面,重点是功能和编程接口。 | | 专家的意见 | 利用 SQL 语言和 RDBMS 概念来设计和开发应用的大型开发者社区。 | 开发这些开源工具的小型开发者社区。 |

2.6 数据库的类别

在这一部分,你将快速探索 NoSQL 的风景。你会看到 NoSQL 数据库的新兴类别。表 2-3 显示了 NoSQL 景观中的几个项目,以及每个类别中的类型和参与者。

表 2-3。

NoSQL Categories

| 种类 | 简要描述 | 例如 | | --- | --- | --- | | 基于文档的 | 数据以文档的形式存储。例如,{Name= "测试用户",Address="Address1 ",年龄:8} | MongoDB | | XML 数据库 | XML 用于存储数据。 | MarkLogic | | 图形数据库 | 数据存储为节点集合。节点通过边连接。节点相当于编程语言中的对象。 | GraphDB(图形数据库) | | 键值存储 | 将数据存储为键值对。 | Cassandra 再说一遍 memcached |

NoSQL 数据库根据数据的存储方式进行分类。NoSQL 大多遵循水平结构,因为需要提供大量精选信息,通常是近实时的。它们针对大规模的插入和检索操作进行了优化,内置了复制和集群功能。

2-4 简要提供了不同类别的 NoSQL 数据库之间的特性比较。

表 2-4。

Feature Comparison

| 特征 | 面向列 | 文档存储 | 键值存储 | 图表 | | --- | --- | --- | --- | --- | | 类似表格的模式支持(列) | 是 | 不 | 不 | 是 | | 完成更新/提取 | 是 | 是 | 是 | 是 | | 部分更新/获取 | 是 | 是 | 是 | 不 | | 对值进行查询/过滤 | 是 | 是 | 不 | 是 | | 跨行聚合 | 是 | 不 | 不 | 不 | | 实体之间的关系 | 不 | 不 | 不 | 是 | | 跨实体视图支持 | 不 | 是 | 不 | 不 | | 批量提取 | 是 | 是 | 是 | 是 | | 批量更新 | 是 | 是 | 是 | 不 |

在考虑 NoSQL 项目时,重要的是您感兴趣的特性集。当决定使用 NoSQL 的产品时,首先你需要非常仔细地理解问题需求,然后你应该看看其他已经使用 NoSQL 产品解决类似问题的人。请记住,NoSQL 仍在不断成熟,因此这将使您能够从同行和以前的部署中学习,并做出更好的 choi ces。

此外,你还需要考虑以下问题。

  • 需要处理的数据有多大?
  • 读写的吞吐量是多少?
  • 如何在系统中实现一致性?
  • 系统需要支持高写性能还是高读性能?
  • 可维护性和管理性有多容易?
  • 需要查询什么?
  • 使用 NoSQL 的好处是什么?

我们建议您从小处着手,但意义重大,并尽可能考虑混合方法。

2.7 摘要

在这一章中,你了解了 NoSQL。您现在应该明白什么是 NoSQL,以及它与 SQL 有何不同。你还研究了 NoSQL 的各种类别。

在接下来的章节中,您将了解 MongoDB,这是一个基于文档的 NoSQL 数据库。

三、MongoDB 简介

"MongoDB is one of NoSQL's leading document storage databases. It enables organizations to process big data and gain meaningful insights from it. "

一些领先的企业和消费者 IT 公司已经在其产品和解决方案中利用了 MongoDB 的功能。MongoDB 3.0 版本引入了可插拔存储引擎和 Ops Manager,这扩展了最适合 MongoDB 的应用集。

MongoDB 的名字来源于单词“humungous”。像其他 NoSQL 数据库一样,MongoDB 也不符合 RDBMS 原则。它没有表、行和列的概念。此外,它不提供 ACID 遵从性、连接、外键等特性。

MongoDB 将数据存储为二进制 JSON 文档(也称为 BSON)。文档可以有不同的模式,这意味着模式可以随着应用的发展而改变。MongoDB 是为可伸缩性、性能和高可用性而构建的。

在这一章中,我们将讨论一下 MongoDB 的创建和设计决策。我们将在接下来的章节中研究 MongoDB 的关键特性、组件和架构。

3.1 历史

2007 年下半年,Dwight Merriman、Eliot Horowitz 和他们的团队决定开发一个在线服务。该服务的目的是为开发、托管和自动扩展 web 应用提供一个平台,这与 Google App Engine 或 Microsoft Azure 等产品非常相似。很快他们意识到没有开源数据库平台适合这项服务的需求。

梅里曼说:“我们觉得许多现有的数据库并没有真正具备你希望它们具备的‘云计算’原则:弹性、可伸缩性,以及……易于管理,同时也便于开发者和运营商使用。”。“[MySQL]不具备所有这些特性。”所以他们决定建立一个不符合 RDBMS 模型的数据库。

一年后,该服务的数据库就可以使用了。该服务本身从未发布,但该团队在 2009 年决定将数据库开源为 MongoDB。2010 年 3 月,MongoDB 1.4.0 的发布被认为是生产就绪的。最新的生产版本是 3.0,于 2015 年 3 月发布。MongoDB 是在纽约初创公司 10gen 的赞助下建立的。

3.2 MongoDB 设计理念

在他的一次演讲中,Eliot Horowitz 提到 MongoDB 不是在实验室中设计的,而是根据构建大规模、高可用性和健壮系统的经验构建的。在这一节中,我们将简要地看一下导致 MongoDB 今天这个样子的一些设计决策。

3.2.1 速度、可扩展性和敏捷性

设计团队在设计 MongoDB 时的目标是创建一个快速、大规模可伸缩且易于使用的数据库。为了在分区数据库中实现速度和水平可伸缩性,正如 CAP 定理中所解释的,一致性和事务支持必须折衷。因此,根据这个定理,MongoDB 以一致性和事务支持为代价提供了高可用性、可伸缩性和分区。实际上,这意味着 MongoDB 使用文档而不是表和行来使其灵活、可伸缩和快速。

非关系方法

传统的 RDBMS 平台使用纵向扩展方法提供可伸缩性,这需要更快的服务器来提高性能。RDBMS 系统中的以下问题导致了 MongoDB 和其他 NoSQL 数据库的设计方式:

  • 为了向外扩展,RDBMS 数据库需要链接两个或更多系统中的可用数据,以便报告结果。这在 RDBMS 系统中很难实现,因为它们被设计成当所有数据都可以一起计算时才工作。因此,数据必须可用于在单个位置进行处理。
  • 在多台主动-主动服务器的情况下,当两台服务器都从多个来源获取更新时,很难确定哪个更新是正确的。
  • 当应用尝试从第二个服务器读取数据,并且信息已经在第一个服务器上更新,但尚未与第二个服务器同步时,返回的信息可能是陈旧的。

MongoDB 团队决定采用非关系方法来解决这些问题。如前所述,MongoDB 将其数据存储在 BSON 文档中,所有相关数据都放在一起,这意味着一切都在一个地方。MongoDB 中的查询基于文档中的键,因此文档可以分布在多个服务器上。查询每个服务器意味着它将检查自己的文档集并返回结果。这实现了线性可伸缩性和改进的性能。

MongoDB 有一个主-从复制,主服务器接受写请求。如果写性能需要提高,那么可以使用分片;这将数据分割到多台机器上,使这些机器能够更新数据集的不同部分。在 MongoDB 中分片是自动的;随着机器数量的增加,数据会自动分发。

基于 JSON 的文档存储

MongoDB 使用基于 JSON(JavaScript 对象表示法)的文档存储来存储数据。JSON/BSON 提供了一个无模式模型,在数据库设计方面提供了灵活性。与 RDBMSs 不同,可以无缝地对模式进行更改。

这种设计还通过在内部将相关数据分组在一起并使其易于搜索来实现高性能。

JSON 文档包含实际数据,相当于 SQL 中的一行。然而,与 RDBMS 行相反,文档可以有动态模式。这意味着集合中的文档可以有不同的字段或结构,或者公共字段可以有不同类型的数据。

文档包含键值对形式的数据。让我们用一个例子来理解这一点:

{

"Name": "ABC",

"Phone": ["1111111",

........"222222"

........],

"Fax":..

}

如上所述,键和值是成对出现的。文档中的键值可以留空。在上面的例子中,文档有三个键,即“姓名”、“电话”和“传真”“传真”键没有价值。

性能与功能对比

为了让 MongoDB 高性能、快速度,RDBMS 系统中常见的某些特性在 MongoDB 中是没有的。MongoDB 是一个面向文档的 DBMS,其中数据存储为文档。它不支持连接,也没有完全一般化的事务。但是,它确实提供了对二级索引的支持,它使用户能够使用查询文档进行查询,并且它提供了对每个文档级别的原子更新的支持。它提供了一个副本集,这是一种具有自动故障转移的主从复制形式,并且它具有内置的水平伸缩功能。

3.2.5 在任何地方运行数据库

一个主要的设计决策是从任何地方运行数据库的能力,这意味着它应该能够运行在服务器、虚拟机甚至使用按使用付费服务的云上。用于实现 MongoDB 的语言是 C++,这使得 MongoDB 能够实现这个目标。10gen 站点为不同的操作系统平台提供了二进制文件,使得 MongoDB 可以在几乎任何类型的机器上运行。

3.3 SQL 比较

以下是 MongoDB 与 SQL 的不同之处。

MongoDB uses documents for storing its data, which offer a flexible schema (documents in same collection can have different fields). This enables the users to store nested or multi-value fields such as arrays, hashes, etc. In contrast, RDBMS systems offer a fixed schema where a column’s value should have a similar data type. Also, it’s not possible to store arrays or nested values in a cell.   MongoDB doesn’t provide support for JOIN operations, like in SQL. However, it enables the user to store all relevant data together in a single document, avoiding at the periphery the usage of JOINs. It has a workaround to overcome this issue. We will be discussing this in more detail in a later chapter.   MongoDB doesn’t provide support for transactions in the same way as SQL. However, it guarantees atomicity at the document level. Also, it uses an isolation operator to isolate write operations that affect multiple documents, but it does not provide “all-or-nothing” atomicity for multi-document write operations.  

3.4 总结

在本章中,您将了解 MongoDB、它的历史以及 MongoDB 系统设计的简要细节。在接下来的章节中,您将了解更多关于 MongoDB 的数据模型。

Footnotes 1

The Register,Cade Metz,“MongoDB daddy:我的宝贝打败了 Google BigTable”,

www.theregister.co.uk/2011/05/25/the_once_and_future_mongodb/ ,2011 年 5 月 25 日。

四、MongoDB 数据模型

“MongoDB 设计用于处理文档,不需要任何预定义的列或数据类型(不像关系数据库),这使得数据模型非常灵活。”

在本章中,您将了解 MongoDB 数据模型。您还将了解灵活模式(多态模式)的含义,以及为什么它是 MongoDB 数据模型的一个重要方面。

4.1 数据模型

在前一章中,您看到了 MongoDB 是一个基于文档的数据库系统,其中的文档可以有一个灵活的模式。这意味着集合中的文档可以有不同(或相同)的字段集。这为您处理数据提供了更大的灵活性。

在本章中,您将探索 MongoDB 灵活的数据模型。在任何需要的地方,我们将展示这种方法与 RDBMS 系统的不同之处。

一个 MongoDB 部署可以有许多数据库。每个数据库都是一组集合。集合类似于 SQL 中的表的概念;然而,它们是无模式的。每个集合可以有多个文档。将文档想象成 SQL 中的一行。图 4-1 描绘了 MongoDB 数据库模型。

A332296_1_En_4_Fig1_HTML.jpg

图 4-1。

MongoDB database model

在 RDBMS 系统中,由于表结构和每列的数据类型是固定的,所以只能在一列中添加特定数据类型的数据。在 MongoDB 中,集合是文档的集合,其中数据存储为键值对。

让我们通过一个例子来理解数据是如何存储在文档中的。以下文档包含用户的姓名和电话号码:

{"Name": "ABC", "Phone": ["1111111", "222222" ] }

动态模式意味着同一集合中的文档可以有相同或不同的字段或结构集,甚至公共字段也可以跨文档存储不同类型的值。在集合的文档中存储数据的方式没有严格的限制。

让我们看一个区域集合的例子:

{ "R_ID" : "REG001", "Name" : "United States" }

{ "R_ID" :1234, "Name" : "New York", "Country" : "United States" }

在这段代码中,区域集合中有两个文档。虽然这两个文档都是一个集合的一部分,但是它们有不同的结构:第二个集合有一个额外的信息字段,即 country。事实上,如果您查看“R_ID”字段,它在第一个文档中存储一个字符串值,而在第二个文档中存储一个数字。

因此,一个集合的文档可以有完全不同的模式。应用需要将特定集合中的文档存储在一起,或者拥有多个集合。

4.1.1 JSON 和 BSON

MongoDB 是一个基于文档的数据库。它使用二进制 JSON 来存储数据。

在本节中,您将了解 JSON 和二进制 JSON (BSON)。JSON 代表 JavaScript 对象符号。它是当今现代网络中用于数据交换的标准(和 XML 一起)。该格式是人和机器可读的。这不仅是交换数据的好方法,也是存储数据的好方法。

JSON 支持所有基本的数据类型(比如字符串、数字、布尔值和数组)。

以下代码显示了 JSON 文档的样子:

{

"_id" : 1,

"name" : { "first" : "John", "last" : "Doe" },

"publications" : [

{

"title" : "First Book",

"year" : 1989,

"publisher" : "publisher1"

},

{ "title" : "Second Book",

"year" : 1999,

"publisher" : "publisher2"

}

]

}

JSON 允许您将所有相关的信息保存在一个地方,这提供了出色的性能。它还使文档的更新变得独立。这是无模式的。

4.1.1.1 二进制 JSON (BSON)

MongoDB 以二进制编码格式存储 JSON 文档。这被称为 BSON。BSON 数据模型是 JSON 数据模型的扩展形式。

MongoDB 对 BSON 文档的实现是快速的、高度可穿越的和轻量级的。它支持在其他数组中嵌入数组和对象,还支持 MongoDB 在对象内部构建索引,并根据查询的表达式匹配对象,包括顶级和嵌套的 BSON 键。

4.1.2 标识符(_id)

您已经看到 MongoDB 将数据存储在文档中。文档由键值对组成。虽然文档可以比作 RDBMS 中的行,但与行不同,文档具有灵活的模式。键只不过是一个标签,可以粗略地比作 RDBMS 中的列名。密钥用于从文档中查询数据。因此,就像 RDBMS 主键(用于惟一标识每一行)一样,您需要有一个惟一标识集合中每个文档的键。这在 MongoDB 中被称为 _id。

如果您没有为一个键显式指定任何值,MongoDB 将自动生成一个惟一的值并分配给它。这个键值是不可变的,可以是除数组之外的任何数据类型。

4.1.3 加盖收藏

您现在已经非常熟悉集合和文档了。我们来谈谈一种特殊类型的集合,叫做封顶集合。

MongoDB 有一个集合封顶的概念。这意味着它按照插入的顺序存储集合中的文档。当集合达到其限制时,文档将按 FIFO(先进先出)顺序从集合中移除。这意味着最近最少插入的文档将首先被删除。

这对于需要自动维护插入顺序以及需要在固定大小后删除记录的用例来说很好。一个这样的用例是在达到一定大小后自动截断的日志文件。

Note

MongoDB 本身使用上限集合来维护其复制日志。Capped 集合保证了按插入顺序保存数据,因此按插入顺序检索数据的查询可以快速返回结果,并且不需要索引。不允许更改文档大小的更新。

4.2 多态模式

既然您已经熟悉了 MongoDB 数据结构的无模式特性,现在让我们来探索多态模式和用例。

多态模式是一种模式,其中集合具有不同类型或模式的文档。这种模式的一个很好的例子是名为 Users 的集合。一些用户文档可能有额外的传真号码或电子邮件地址,而其他用户文档可能只有电话号码,然而所有这些文档都共存于同一个用户集合中。这种模式通常被称为多态模式。

在本章的这一部分,你将探究使用多态模式的各种原因。

面向对象的编程

面向对象编程使您能够使用继承让类共享数据和行为。它还允许您在父类中定义可以在子类中覆盖的函数,从而在不同的上下文中以不同的方式运行。换句话说,您可以使用相同的函数名来操作子类和父类,尽管在底层实现可能会有所不同。这种特性被称为多态性。

在这种情况下,要求能够拥有一个模式,其中所有相关的对象集或层次结构中的对象集可以放在一起,并且还可以进行相同的检索。

让我们考虑一个例子。假设您有一个应用,允许用户上传和共享不同的内容类型,如 HTML 页面、文档、图像、视频等。尽管许多字段在上述所有内容类型中都是通用的(如姓名、ID、作者、上传日期和时间),但并非所有字段都是相同的。例如,对于图像,您有一个保存图像内容的二进制字段,而 HTML 页面有一个保存 HTML 内容的大文本字段。

在这个场景中,可以使用 MongoDB 多态模式,其中所有的内容节点类型都存储在同一个集合中,比如 LoadContent,每个文档只有相关的字段。

// "Document collections" - "HTMLPage" document

{

_id: 1,

title: "Hello",

type: "HTMLpage",

text: "<html>Hi..Welcome to my world</html>"

}

...

// Document collection also has a "Picture" document

{

_id: 3,

title: "Family Photo",

type: "JPEG",

sizeInMB: 10,........

}

这种模式不仅使您能够将不同结构的相关数据存储在同一个集合中,还简化了查询。同一个集合可用于对常见字段执行查询,例如获取在特定日期和时间上传的所有内容,以及对特定字段执行查询,例如查找大小大于 X MB 的图像。

因此,面向对象编程是具有多态模式有意义的用例之一。

模式演变

当您使用数据库时,您需要考虑的最重要的事项之一是模式演变(即模式的变化对正在运行的应用的影响)。设计应该以对应用影响最小或没有影响的方式进行,也就是说没有或只有很少的停机时间,没有或只有很少的代码更改,等等。

通常,模式演变是通过执行迁移脚本将数据库模式从旧版本升级到新版本来实现的。如果数据库不在生产环境中,脚本可以简单地删除并重新创建数据库。但是,如果数据库在生产环境中,并且包含实时数据,迁移脚本将会很复杂,因为需要保留数据。剧本应该考虑到这一点。虽然 MongoDB 提供了一个更新选项,如果添加了一个新的字段,可以使用它来更新集合中所有文档的结构,但是想象一下,如果集合中有数千个文档,那么这样做会产生什么影响。它会非常慢,并且会对底层应用的性能产生负面影响。其中一种方法是将新的结构包含到添加到集合中的新文档中,然后在应用仍在运行时逐渐在后台迁移集合。这是拥有多态模式将带来优势的众多用例之一。

例如,假设您正在处理一个票据集合,其中有包含票据详细信息的文档,如下所示:

// "Ticket1" document (stored in "Tickets" collection")

{

_id: 1,

Priority: "High",

type: "Incident",

text: "Printer not working"

}...........

在某个时候,应用团队决定在票据文档结构中引入一个“简短描述”字段,所以最好的替代方法是在新的票据文档中引入这个新字段。在应用中,嵌入一段代码来处理检索“旧样式”文档(没有简短描述字段)和“新样式”文档(有简短描述字段)。旧样式文档可以逐渐迁移到新样式文档。迁移完成后,如果需要,可以更新代码以删除嵌入的用于处理缺失字段的代码。

4.3 总结

在本章中,您学习了 MongoDB 数据模型。您还了解了标识符和上限集合。在本章结束时,您已经理解了灵活的模式是如何帮助您的。

在下一章中,您将开始使用 MongoDB。您将执行 MongoDB 的安装和配置。

五、MongoDB 安装和配置

"MongoDB is a cross-platform database."

在本章中,您将了解在 Windows 和 Linux 上安装 MongoDB 的过程。

5.1 选择您的版本

MongoDB 运行在大多数平台上。MongoDB 下载页面上的 www.mongodb.org/downloads 提供了所有可用包的列表。

适合您环境的正确版本将取决于服务器的操作系统和处理器的种类。MongoDB 支持 32 位和 64 位架构,但是建议在您的生产环境中使用 64 位架构。

32-bit limitation

这是因为在 MongoDB 中使用了内存映射文件。这将 32 位版本的数据限制在 2GB 左右。出于性能原因,建议在生产环境中使用 64 位版本。

在撰写本书时,最新的 MongoDB 生产版本是 3.0.4。MongoDB 的下载适用于 Linux、Windows、Solaris 和 Mac OS X。

MongoDB 下载页面分为以下几个部分:

  • 当前稳定版本(3 . 0 . 4)–2015 年 6 月 16 日
  • 以前的版本(稳定)
  • 开发版本(不稳定)

当前版本是目前最稳定的最新版本,在撰写本书时是 3.0.4。当一个新版本发布时,先前的稳定版本被移到先前版本部分。

开发版本,顾名思义,是仍在开发中的版本,因此被标记为不稳定。这些版本可以有额外的功能,但它们可能不稳定,因为它们仍处于开发阶段。您可以使用开发版本来尝试新功能,并向 10gen 提供有关功能和面临的问题的反馈。

5.2 在 Linux 上安装 MongoDB

本节介绍在 LINUX 系统上安装 MongoDB。在下面的演示中,我们将使用 Ubuntu Linux 发行版。您可以手动或通过存储库安装 MongoDB。我们将向您介绍这两个选项。

5.2.1 使用资料库安装

在 LINUX 中,存储库是包含软件的在线目录。Aptitude 是用来在 Ubuntu 上安装软件的程序。尽管 MongoDB 可能存在于默认的存储库中,但也有可能是过时的版本,所以第一步是配置 Aptitude 来查看定制的存储库。

Issue the following to import the public.GPG key for MongoDB: sudo apt-key adv --keyserver hkp://keyserver.ubuntu.com:80 --recv 7F0CEB10   Next, use the following command to create the /etc/apt/sources.list.d/mongodb-org-3.0.list file: echo "deb http://repo.mongodb.org/apt/ubuntu "$(lsb_release -sc)"/mongodb-org/3.0 multiverse" | sudo tee /etc/apt/sources.list.d/mongodb-org-3.0.list   Finally, use the following command to reload the repository: sudo apt-get update Now Aptitude is aware of the manually added repository.   Next, you need to install the software. The following command should be issued in the shell to install MongoDB’s current stable version: sudo apt-get install -y mongodb-org  

您已经成功安装了 MongoDB,这就是它的全部内容。

手动安装

在本节中,您将看到如何手动安装 MongoDB。这一知识在以下情况下很重要:

  • 当 Linux 发行版不使用 Aptitude 时。
  • 当您需要的版本无法通过存储库获得或者不属于存储库时。
  • 当你需要同时运行多个 MongoDB 版本时。

手动安装的第一步是决定要使用的 MongoDB 版本,然后从站点下载。接下来,需要使用以下命令提取包:

$ tar -xvf mongodb-linux-x86_64-3.0.4.tgz

mongodb-linux-i686-3.0.4/THIRD-PARTY-NOTICES

mongodb-linux-i686-3.0.4/GNU-AGPL-3.0

mongodb-linux-i686-3.0.4/bin/mongodump

............

mongodb-linux-i686-3.0.4/bin/mongosniff

mongodb-linux-i686-3.0.4/bin/mongod

mongodb-linux-i686-3.0.4/bin/mongos

mongodb-linux-i686-3.0.4/bin/mongo

这会将包内容提取到一个新目录中,即mongodb-linux-x86_64-3.0.4(位于当前目录下)。该目录包含许多子目录和文件。主可执行文件在子目录bin下。

这就成功完成了 MongoDB 的安装。

5.3 在 Windows 上安装 MongoDB

在 Windows 上安装 MongoDB 很简单,只需下载所选 Windows 版本的 msi 文件并运行安装程序。

安装程序将引导您完成 MongoDB 的安装。

按照向导,您将到达选择安装类型屏幕。有两种安装类型可供您自定义安装。在本例中,选择“自定义”安装类型。

选择自定义时需要指定安装目录,所以将目录指定到C:\PracticalMongoDB

注意,MongoDB 可以从用户选择的任何文件夹中运行,因为它是自包含的,不依赖于系统。如果选择了完整的设置类型,则默认选择的文件夹是C:\Program Files\MongoDB

单击“下一步”将带您进入“准备安装”屏幕。单击安装。

这将开始安装,并在屏幕上显示进度。安装完成后,向导将带您进入完成屏幕。

单击“完成”完成设置。成功完成上述步骤后,您就有了一个名为C:\PracticalMongoDB的目录,在bin文件夹中有所有相关的应用。这就是全部了。

5.4 运行 MongoDB

让我们看看如何开始运行和使用 MongoDB。

先决条件

存储文件需要一个数据文件夹。这在 Windows 中默认为C:\data\db,在 LINUX 系统中默认为/data/db

这些数据目录不是由 MongoDB 创建的,因此在启动 MongoDB 之前,需要手动创建数据目录,并且您需要确保设置了适当的权限(例如 MongoDB 具有读、写和目录创建权限)。

如果在创建文件夹之前启动 MongoDB,它将抛出一条错误消息,并且无法运行。

5.4.2 启动服务

一旦创建了目录并获得了权限,就执行 mongod 应用(位于bin目录下)来启动 MongoDB 核心数据库服务。

继续上述安装,可以通过在 Windows 中打开命令提示符(需要以管理员身份运行)并执行以下命令来启动:

c:\> c:\practicalmongodb\bin\mongod.exe

对于 Linux,mongod 进程是在 shell 中启动的。

这将在本地主机界面上启动 MongoDB 数据库。它将在端口 27017 上侦听来自 mongo shell 的连接。

如上所述,需要在启动数据库之前创建文件夹路径,默认情况下是c:\data\db。使用–dbpath 参数启动数据库服务时,也可以提供备用路径。

C``:\>``C:\``practicalmongodb

C:\NewDBPath\DBContents

5.5 验证安装

相关的可执行文件将出现在子目录bin下。为了检查安装步骤是否成功,可以在bin目录下检查以下内容:

  • Mongod:核心数据库服务器
  • Mongo:数据库 Shell
  • Mongos:自动分片过程
  • Mongoexport:导出实用程序
  • Mongoimport:导入实用程序

除此之外,bin文件夹中还有其他应用。

mongo 应用启动 mongo shell,它提供对数据库内容的访问,并允许您对 MongoDB 中的数据执行选择性查询或聚合。

如上所述,mongod 应用用于启动数据库服务或守护进程。

启动应用时,可以设置多个标志。例如,–dbpath可用于指定存储数据库文件的替代路径。要获得所有可用选项的列表,请在启动服务时包含--help标志。

5.6 蒙戈布 Shell

mongo shell 是 MongoDB 标准发行版的一部分。shell 为 MongoDB 提供了一个完整的数据库接口,使您能够使用 JavaScript 环境处理存储在 MongoDB 中的数据,该环境可以完全访问该语言和所有标准函数。

一旦数据库服务启动,您就可以启动 mongo shell 并开始使用 MongoDB。这可以使用 Linux 中的 Shell 或 Windows 中的命令提示符来完成(以管理员身份运行)。

您必须指出可执行文件的确切位置,例如在 Windows 环境中的C:\practicalmongodb\bin\文件夹中。

打开命令提示符(以管理员身份运行)并键入mongo.exe。按回车键。这将启动 mongo shell。

C:\> C:\practicalmongodb\bin\mongo.exe

MongoDB shell version: 3.0.4

connecting to: test

>

如果在启动服务时没有指定参数,它将连接到 localhost 实例上名为test的默认数据库。

当连接到数据库时,将自动创建数据库。如果试图访问一个不存在的数据库,MongoDB 提供了自动创建数据库的特性。

下一章提供了更多关于使用 mongo shell 的信息。

5.7 保护部署

您知道如何通过默认配置安装和开始使用 MongoDB。接下来,您需要确保存储在数据库中的数据在各个方面都是安全的。

在本节中,您将了解如何保护您的数据。您将更改默认安装的配置,以确保您的数据库更加安全。

5.7.1 使用认证和授权

身份验证验证用户的身份,而授权确定用户可以对经过身份验证的数据库执行的操作级别。

这意味着用户只有在使用有权访问数据库的凭证登录时才能访问数据库。这将禁用对数据库的匿名访问。用户通过身份验证后,可以使用授权来确保用户只拥有完成手头任务所需的访问权限。

身份验证和授权都存在于每个数据库级别。用户存在于单个逻辑数据库的上下文中。

用户信息保存在 admin 数据库中的一个名为system.users的集合中。该集合维护对用户进行身份验证所需的凭证,其中存储了用户 id、密码和创建该集合所依据的数据库,以及授权用户所需的特权。

MongoDB 使用基于角色的方法进行授权(read、readWrite、readAnyDatabase 等角色)。).如果需要,用户管理员可以创建自定义角色。

system.users集合中的特权文档用于存储每个用户角色。同一文档维护经过身份验证的用户的凭证。

system.users集合中的文档示例如下:

{

_id : "practicaldb.Shaks",

user : "Shaks",

db : "practicaldb",

credentials : {.......},

roles : [

{ role: "read", db: "practicaldb" },

{ role: "readWrite", db: "MyDB" }

],

......

}

这个文档告诉我们,用户 Shaks 与数据库practicaldb相关联,它在practicaldb数据库中有读取角色,在MyDB数据库中有读写角色。请注意,用户名和关联的数据库唯一地标识了 MongoDB 中的一个用户,因此,如果有两个同名的用户,但是他们与不同的数据库相关联,那么他们将被视为两个唯一的用户。因此,一个用户可以在不同的数据库上拥有多个具有不同授权级别的角色。

可用的角色有

  • read:这提供了对指定数据库的所有集合的只读访问。
  • readWrite:这提供了对指定数据库中任何集合的读写访问。
  • dbAdmin:这使用户能够在指定的数据库中执行管理操作,例如使用 ensureIndex、dropIndexes、reIndex、indexStats 进行索引管理、重命名集合、创建集合等。
  • userAdmin:这使用户能够对指定数据库的system.users集合执行读写操作。它还支持更改现有用户的权限或创建新用户。这实际上是指定数据库的超级用户角色。
  • clusterAdmin:该角色使用户能够授予更改或显示整个系统信息的管理操作的访问权限。clusterAdmin 仅适用于管理数据库。
  • readAnyDatabase:该角色使用户能够读取 MongoDB 环境中的任何数据库。
  • readWriteAnyDatabase:这个角色类似于 readWrite,只是它适用于所有数据库。
  • userAdminAnyDatabase:该角色类似于 userAdmin 角色,只是它适用于所有数据库。
  • dbAdminAnyDatabase:该角色与 dbAdmin 相同,只是它适用于所有数据库。
  • 从 2.6 版开始,用户管理员还可以创建用户定义的角色,通过提供集合级别和命令级别的访问权限来遵守最低权限策略。用户定义角色的范围是创建它的数据库,并且由数据库和角色名称的组合唯一标识。所有用户定义的角色都存储在system.roles集合中。
5.7.1.1 启用身份验证

默认情况下,身份验证是禁用的,因此使用--auth来启用身份验证。启动 mongod 的同时,使用mongod --auth。在启用身份验证之前,您需要至少有一个管理员用户。如上所述,管理员用户是负责创建和管理其他用户的用户。

建议在生产部署中,此类用户仅用于管理用户,不应用于任何其他角色。在 MongoDB 部署中,这个用户是需要创建的第一个用户;该用户可以创建系统的其他用户。

管理员用户可以通过两种方式创建:在启用身份验证之前或之后。

在本例中,您将首先创建 admin 用户,然后启用 auth 设置。以下步骤应在 Windows 平台上执行。

使用默认设置启动 mongod:

C:\>C:\practicalmongodb\bin\mongod.exe

C:\practicalmongodb\bin\mongod.exe --help for help and startup options

2015-07-03T23:11:10.716-0700 I CONTROL Hotfix KB2731284 or later update is installed, no need to zero out data files

2015-07-03T23:11:10.716-0700 I JOURNAL [initandlisten] journal dir=C:\data\db\journal

...................................................

2015-07-03T23:11:10.763-0700 I CONTROL [initandlisten] MongoDB starting : pid=2776 port=27017 dbpath=C:\data\db\ 64-bit host=ANOC9

2015-07-03T23:11:10.763-0700 I CONTROL [initandlisten] targetMinOS: Windows 7/W

indows Server 2008 R2

2015-07-03T23:11:10.763-0700 I CONTROL [initandlisten] db version v3.0.4

2015-07-03T23:11:10.764-0700 I CONTROL [initandlisten] OpenSSL version: OpenSSL 1.0.1j-fips 19 Mar 2015

2015-07-03T23:11:10.764-0700 I CONTROL [initandlisten] build info: windows sys. getwindowsversion(major=6, minor=1, build=7601, platform=2, service_pack='Service Pack 1') BOOST_LIB_VERSION=1_49

2015-07-03T23:11:10.771-0700 I NETWORK [initandlisten] waiting for connections

on port 27017

5.7.1.2 创建管理员用户

以管理员身份运行命令提示符的另一个实例,并执行 mongo 应用:

C:\> C:\practicalmongodb\bin\mongo.exe

MongoDB shell version: 3.0.4

connecting to: test

>

切换到管理数据库

Note

admin db 是一个特权数据库,用户需要访问它才能执行某些管理命令,比如创建 admin 用户。

> db = db.getSiblingDB('admin')

管理

需要使用以下角色之一创建用户:userAdminAnyDatabase 或 userAdmin:

>db.createUser({user: "AdminUser", pwd: "password", roles:["userAdminAnyDatabase"]})

Successfully added user: { "user" : "AdminUser", "roles" : [ "userAdminAnyDatabase" ] }

接下来,使用该用户进行身份验证。使用auth设置重启 mongod:

C:\> C:\practicalmongodb\bin\mongod.exe -auth

C:\practicalmongodb\bin\mongod.exe --help for help and startup options

2015-07-03T23:11:10.716-0700 I CONTROL Hotfix KB2731284 or later update is installed, no need to zero out data files

2015-07-03T23:11:10.716-0700 I JOURNAL [initandlisten] journal dir=C:\data\db\journal

...................................................

2015-07-03T23:11:10.763-0700 I CONTROL [initandlisten] MongoDB starting : pid=2776 port=27017 dbpath=C:\data\db\ 64-bit host=ANOC9

2015-07-03T23:11:10.763-0700 I CONTROL [initandlisten] targetMinOS: Windows 7/W

indows Server 2008 R2

2015-07-03T23:11:10.763-0700 I CONTROL [initandlisten] db version v3.0.4

2015-07-03T23:11:10.764-0700 I CONTROL [initandlisten] OpenSSL version: OpenSSL 1.0.1j-fips 19 Mar 2015

2015-07-03T23:11:10.764-0700 I CONTROL [initandlisten] build info: windows sys. getwindowsversion(major=6, minor=1, build=7601, platform=2, service_pack='Service Pack 1') BOOST_LIB_VERSION=1_49

2015-07-03T23:11:10.771-0700 I NETWORK [initandlisten] waiting for connections

on port 27017

启动 mongo 控制台,使用上面创建的 AdminUser 用户对管理数据库进行身份验证:

C:\>c:\practicalmongodb\bin\mongo.exe

MongoDB shell version: 3.0.4

connecting to: test

>use admin

switched to db admin

>db.auth("AdminUser", "password")

1

>

5.7.1.3 创建用户并启用授权

在本节中,您将创建一个用户,并为新创建的用户分配一个角色。您已经使用 admin 用户进行了身份验证,如下所示:

C:\>c:\practicalmongodb\bin\mongo.exe

MongoDB shell version: 3.0.4

connecting to: test

>use admin

switched to db admin

>db.auth("AdminUser", "password")

1

>

切换到Product数据库,创建用户 Alice,并分配对产品数据库的读取权限,如下所示:

> use product

switched to db product

>db.createUser({user: "Alice"

... , pwd:"Moon1234"

... , roles: ["read"]

... }

... )

Successfully added user: { "user" : "Alice", "roles" : [ "read" ] }

接下来,验证用户对数据库具有只读访问权限:

>db

product

>show users

{

"_id" : "product.Alice",

"user" : "Alice",

"db" : "product",

"roles" : [

{

"role" : "read",

"db" : "product"

}

]

}

接下来,连接到一个新的 mongo 控制台,以 Alice 的身份登录到Products数据库,发出只读命令:

C:\> c:\practicalmongodb\bin\mongo.exe -u Alice -p Moon1234 product

2015-07-03T23:11:10.716-0700 I CONTROL Hotfix KB2731284 or later update is installed, no need to zero-out data files

MongoDB shell version: 3.0.4

connecting to: products

Post successful authentication the following entry will be seen on the mongod console.

2015-07-03T23:11:26.742-0700 I ACCESS [conn2] Successfully authenticated as principal Alice on product

控制对网络的访问

默认情况下,mongod 和 mongos 绑定到系统上所有可用的 IP 地址。在本节中,您将了解用于限制网络暴露的配置选项。以下代码在 Windows 平台上执行:

C:\> c:\practicalmongodb\bin\mongod.exe --bind_ip 127.0.0.1 --port 27017 --rest

2015-07-03T00:33:49.929-0700 I CONTROL Hotfix KB2731284 or later update is installed, no need to zero out data files

2015-07-03T00:33:49.946-0700 I JOURNAL [initandlisten] journal dir=C:\data\db\journal

2015-07-03T00:33:49.980-0700 I CONTROL [initandlisten] MongoDB starting : pid=1144 port=27017 dbpath=C:\data\db\ 64-bit host=ANOC9

2015-07-03T00:33:49.980-0700 I CONTROL [initandlisten] targetMinOS: Windows 7/Windows Server 2008 R2

2015-07-03T00:33:49.980-0700 I CONTROL [initandlisten] db version v3.0.4

2015-07-03T00:33:49.980-0700 I CONTROL [initandlisten] OpenSSL version: OpenSSL1.0.1j-fips 19 Mar 2015

2015-07-03T00:33:49.980-0700 I CONTROL [initandlisten] build info: windows sys.getwindowsversion(major=6, minor=1, build=7601, platform=2, service_pack='Service Pack 1') BOOST_LIB_VERSION=1_49

2015-07-03T00:33:49.981-0700 I CONTROL [initandlisten] allocator: system

2015-07-03T00:33:49.981-0700 I CONTROL [initandlisten] options: { net: { bindIp: "127.0.0.1", http: { RESTInterfaceEnabled: true, enabled: true }, port: 27017} }

2015-07-03T00:33:49.990-0700 I NETWORK [initandlisten] waiting for connections on port 27017

2015-07-03T00:33:49.990-0700 I NETWORK [websvr] admin web console waiting for connections on port 28017

2015-07-03T00:34:22.277-0700 I NETWORK [initandlisten] connection accepted from 127.0.0.1:49164 #1 (1 connection now open)

您已经用bind_ip启动了服务器,它有一个值设置为 127.0.0.1,这是 localhost 接口。

bind_ip限制程序将监听的输入连接的网络接口。可以指定逗号分隔的 IP 地址。在您的例子中,您已经将 mongod 限制为只监听 localhost 接口。

当 mongod 实例启动时,默认情况下,它会在端口 27017 上等待任何传入的连接。您可以使用–port进行更改。

仅仅改变端口并不能降低太多风险。为了完全保护环境,您需要仅允许受信任的客户端使用防火墙设置连接到端口。

更改此端口也会更改 HTTP 状态接口端口,默认端口为 28017。此端口在 X+1000 端口上可用,其中 X 代表连接端口。

此网页公开了诊断和监控信息,其中包括运行数据、各种日志和有关数据库实例的状态报告。它提供可用于管理目的的管理级统计数据。默认情况下,此页面是只读的;为了使它完全交互,您将使用 REST 设置。这种配置使页面具有充分的交互性,有助于管理员解决任何性能问题。使用防火墙时,应该只允许可信客户端访问此端口。

建议在生产环境中禁用 HTTP 状态页面以及 REST 配置。

5.7.2.1 使用防火墙

防火墙用于控制网络中的访问。它们可用于允许从特定 IP 地址到特定 IP 端口的访问,或阻止来自任何不受信任主机的任何访问。它们可以用来为 mongod 实例创建一个可信的环境,在这个环境中,您可以指定哪些 IP 地址或主机可以连接到 mongod 的哪些端口或接口。

在 Windows 平台上,使用netsh为端口 27017 配置传入流量:

C:\> netsh advfirewall firewall add rule name="Open mongod port 27017" dir=in action=allow protocol=TCP localport=27017

Ok.

C:\>

这段代码说明所有的传入流量都允许通过端口 27017,因此任何应用服务器都可以连接到 mongod。

5.7.2.2 加密数据

您已经看到 MongoDB 将其所有数据存储在一个数据目录中,该目录在 Windows 中默认为C:\data\db/data/db。文件以不加密的方式存储在目录中,因为在 Mongo 中没有提供自动加密文件的方法。任何具有文件系统访问权限的攻击者都可以读取文件中存储的数据。应用有责任确保敏感信息在写入数据库之前被加密。

此外,应实施操作系统级机制,如文件系统级加密和权限,以防止对文件的未授权访问。

5.7.2.3 加密通信

通常要求 mongod 和客户机(例如 mongo shell)之间的通信是加密的。在这个设置中,您将看到如何通过配置 SSL 为上述安装增加一个安全级别,以便 mongod 和 mongo shell(客户机)之间的通信使用 SSL 证书和密钥。

建议使用 SSL 进行服务器和客户端之间的通信。

从版本 3.0 开始,大多数 MongoDB 发行版现在都支持 SSL。以下命令在 Windows 平台上执行。

第一步是生成包含公钥证书和私钥的.pem文件。MongoDB 可以使用自签名证书或由证书颁发机构颁发的任何有效证书。

在本书中,您将使用以下命令来生成自签名证书和私钥。

Install OpenSSL and Microsoft Visual C++ 2008 redistributable as per the MongoDB distribution and the Windows platform. In this book, you have installed the 64-bit version.   Run the following command to create a public key certificate and a private key: C:\> cd c:\OpenSSL-Win64\bin C:\OpenSSL-Win64\bin\>openssl This opens the OpenSSL shell where you need to enter the following command: OpenSSL>req -new -x509 -days 365 -nodes -out C:\practicalmongodb\mongodb-cert.crt -keyout C:\practicalmongodb\mongodb-cert.key The above step generates a certificate key named mongodb-cert.key and places it in the C:\practicalmongodb folder.   Next, you need to concatenate the certificate and the private key to the .pem file. In order to achieve this, run the following commands at the command prompt: C:\> more C:\practicalmongodb\mongodb-cert.key > temp C:\> copy \b temp C:\practicalmongodb\mongodb-cert.crt > C:\practicalmongodb\mongodb.pem  

现在你有了一个.pem文件。启动 mongod 时使用以下运行时选项:

C:\> C:\practicalmongodb\bin\mongod –sslMode requireSSL --sslPEMKeyFile C:\practicalmongodb\mongodb.pem

2015-07-03T03:45:33.248-0700 I CONTROL Hotfix KB2731284 or later update is installed, no need to zero-out data files

2015-07-03T02:54:30.630-0700 I JOURNAL [initandlisten] journal dir=C:\data\db\journal

2015-07-03T02:54:30.670-0700 I CONTROL [initandlisten] MongoDB starting : pid=2

816 port=27017 dbpath=C:\data\db\ 64-bit host=ANOC9

2015-07-03T02:54:30.670-0700 I CONTROL [initandlisten] targetMinOS: Windows 7/Windows Server 2008 R2

2015-07-03T02:54:30.670-0700 I CONTROL [initandlisten] db version v3.0.4

2015-07-03T02:54:30.670-0700 I CONTROL [initandlisten] OpenSSL version: OpenSSL1.0.1j-fips 19 Mar 2015

2015-07-03T02:54:30.670-0700 I CONTROL [initandlisten] build info: windows sys. getwindowsversion(major=6, minor=1, build=7601, platform=2, service_pack='Service Pack 1') BOOST_LIB_VERSION=1_49

2015-07-03T02:54:30.671-0700 I CONTROL [initandlisten] allocator: system

2015-07-03T02:54:30.671-0700 I CONTROL [initandlisten] options: { net: { ssl: {

PEMKeyFile: "c:\practicalmongodb\mongodb.pem", mode: "requireSSL" } } }

2015-07-03T02:54:30.680-0700 I NETWORK [initandlisten] waiting for connections

on port 27017 ssl

2015-07-03T03:33:43.708-0700 I NETWORK [initandlisten] connection accepted from

127.0.0.1:49194 #2 (1 connection now open)

Note

不建议在生产环境中使用自签名证书,除非它是一个受信任的网络,因为它会使您容易受到中间人攻击。

接下来,您将使用 mongo shell 连接到上面的 mongod。当您使用–ssl选项运行 mongo 时,您需要指定–sslAllowInvalidCertificates–sslCAFile。还是用–sslAllowInvalidCertificates吧。

打开终端窗口,输入以下内容:

C:\> C:\practicalmongodb\bin>mongo --ssl --sslAllowInvalidCertificates

2015-07-03T02:30:10.774-0700 I CONTROL Hotfix KB2731284 or later update is installed, no need to zero-out data files

MongoDB shell version: 3.0.4

connecting to: test

5.8 使用 MongoDB 云管理器进行配置

在本章的开始,您学习了如何使用 Windows 和 Linux 安装和配置 MongoDB。在本章的这一部分,你将看到如何使用 MongoDB 云管理器。

Mongo DBCloud Manager 是数据库开发者内置的一个监控解决方案。在 2.6 版之前,MongoDB 云管理器(以前称为 MongoDB 监控服务或 MMS)仅用于监控和管理 MongoDB。从 2.6 版开始,MongoDB Cloud Manager 引入了一些重要的增强功能,包括备份、时间点恢复和自动化特性,使得操作 MongoDB 的任务比以前更简单。自动化特性为管理员提供了强大的功能,只需点击几下鼠标就可以快速创建、升级、扩展或关闭 MongoDB 实例。

在本书的这一部分,您将看到如何开始使用 MongoDB 云管理器。您将使用 MongoDB 云管理器在 AWS 上部署一个独立的 MongoDB 实例。

当您启动 MongoDB 云管理器时,它会要求在每台服务器上安装一个自动化代理,然后由 MongoDB 云管理器用来与服务器通信。

为了开始供应,您首先需要在 MongoDB Cloud Manager 上创建您的配置文件。

输入以下网址: https://cloud.mongodb.com 。根据您是否拥有帐户,点按“登录”或“免费注册”按钮。

由于您是第一次开始,请单击“注册免费”按钮。这将使您进入图 5-1 所示的页面。

A332296_1_En_5_Fig1_HTML.jpg

图 5-1。

Account Profile

您将创建一个新的个人资料。然而,MongoDB 提供了作为现有云管理器组加入的选项。

输入所有相关细节,如图 5-1 所示,点击继续。这会将您转到提供公司信息的页面。完成个人资料和公司信息后,接受条款并单击创建帐户按钮。这就完成了概要文件的创建。下一步是创建一个组(图 5-2 )。

A332296_1_En_5_Fig2_HTML.jpg

图 5-2。

Create Group

为该组提供一个唯一的名称,然后单击创建组。接下来是部署选择页面,如图 5-3 所示,您可以选择构建新的部署或管理现有的部署。

A332296_1_En_5_Fig3_HTML.jpg

图 5-3。

Deployment

选择以构建新部署。接下来,将提示您构建部署的位置(即本地、AWS 或其他远程环境)。在本例中,选择 AWS。单击“在 AWS 中部署”选项将引导您在自行调配和使用 Cloud Manager 进行调配之间进行选择。

选择“I will Provision”选项,这意味着您将使用已经在 AWS 上提供给您的机器。

下一个屏幕提供了部署类型的选项(即独立、副本集或分片集群)。您正在进行独立部署,因此请单击“创建独立”框。这将使您进入图 5-4 所示的屏幕。

A332296_1_En_5_Fig4_HTML.jpg

图 5-4。

Details for a standalone instance

提供实例名和数据目录前缀,然后单击继续。接下来是如图 5-5 所示的屏幕,它提示您在每台服务器上安装一个自动化代理

A332296_1_En_5_Fig5_HTML.jpg

图 5-5。

Installing an automation agent

该屏幕有一个指定服务器数量的选项。在本例中,您指定 1。

接下来,您需要指定平台。选择 Ubuntu。然后出现图 5-6 中的屏幕。

A332296_1_En_5_Fig6_HTML.jpg

图 5-6。

Automation agent installation instructions

按照步骤操作。

在实现启动代理的步骤之前,需要确保所有相关端口都是打开的(443、4949、27000 到 27018)。

完成所有步骤后,单击“验证代理”按钮。验证后,如果一切正常,您会看到一个继续按钮。

当您单击 Continue 时,您将转到图 5-7 所示的审查和部署页面,在这里您可以看到将要部署的所有流程。在这里,自动化代理下载并安装监控和备份代理。

A332296_1_En_5_Fig7_HTML.jpg

图 5-7。

Review and deploy

单击 Deploy 按钮将转到 deployment 页面,部署更改状态为“正在进行”安装完成后,部署状态将变为“目标状态”,配置的服务器将出现在拓扑视图中。

如果您的部署支持 SSL 或使用任何身份验证机制,您需要手动下载并安装监控代理。

为了检查所有代理是否正常工作,您可以单击控制台上的管理选项卡。

云管理器可以在任何连接互联网的服务器上部署 MongoDB 副本集、分片集群和独立服务器。服务器只需要能够与云管理器建立出站 TCP 连接。

5.9 总结

在本章中,您学习了如何在 Windows 和 Linux 平台上安装 MongoDB。您还了解了一些重要的配置,这些配置对于确保数据库的安全使用是必要的。您通过使用 MongoDB 云管理器进行配置结束了这一章。

在下一章中,您将开始使用 MongoDB Shell。

六、使用 MongoDB Shell

"mongo shell comes with the standard distribution of MongoDB. It provides a JavaScript environment with full access to the language and standard functions. It provides a complete interface for MongoDB database. "

在本章中,您将学习 mongo shell 的基础知识以及如何使用它来管理 MongoDB 文档。在深入研究创建与数据库交互的应用之前,理解 MongoDB shell 的工作原理是很重要的。

没有比从 MongoDB shell 开始体验 MongoDB 数据库更好的方法了。MongoDB shell 简介分为三个部分,以便读者更容易理解和实践这些概念。

第一部分涵盖了数据库的基本特性,包括基本的 CRUD 操作符。下一节将介绍高级查询。本章的最后一节解释了存储和检索数据的两种方式:嵌入和引用。

6.1 基本查询

本节将简要讨论 CRUD 操作(创建、读取、更新和删除)。通过使用基本的示例和练习,您将学习如何在 MongoDB 中执行这些操作。此外,您将了解在 MongoDB 中查询是如何执行的。

与用于查询的传统 SQL 不同,MongoDB 使用自己的类似 JSON 的查询语言从存储的数据中检索信息。

成功安装 MongoDB 后,如第 5 章所述,您将导航到目录C:\practicalmongodb\bin\。该文件夹包含运行 MongoDB 的所有可执行文件。

MongoDB shell 可以通过执行 mongo 可执行文件来启动。

第一步总是启动数据库服务器。打开命令提示符(以管理员身份运行)并发出命令CD \

接下来,运行命令C:\practicalmongodb\bin\mongod.exe。(如果安装在其他文件夹中,路径会相应改变。对于本章中的示例,安装在C:\practicalmongodb文件夹中。)这将启动数据库服务器。

C:\>c:\practicalmongodb\bin\mongod.exe

2015-07-06T02:29:24.501-0700 I CONTROL Hotfix KB2731284 or later update is insalled, no need to zero-out data files

2015-07-06T02:29:24.522-0700 I JOURNAL [initandlisten] journal dir=c:\data\db\ournal

....................................................

2015-07-06T02:29:24.575-0700 I CONTROL [initandlisten] MongoDB starting : pid=384 port=27017 dbpath=c:\data\db\ 64-bit host=ANC09

2015-07-06T02:29:24.575-0700 I CONTROL [initandlisten] targetMinOS: Windows 7/windows Server 2008 R2

2015-07-06T02:29:24.575-0700 I CONTROL [initandlisten] db version v3.0.4

2015-07-06T02:29:24.575-0700 I CONTROL [initandlisten] OpenSSL version: OpenSSL1.0.1j-fips 19 Mar 2015

2015-07-06T02:29:24.575-0700 I CONTROL [initandlisten] build info: windows sys getwindowsversion(major=6, minor=1, build=7601, platform=2, service_pack='Service Pack 1') BOOST_LIB_VERSION=1_49

2015-07-06T02:29:24.575-0700 I CONTROL [initandlisten] allocator: system

2015-07-06T02:29:24.575-0700 I CONTROL [initandlisten] options: {}

2015-07-06T02:29:24.584-0700 I NETWORK [initandlisten] waiting for connections on port 27017

默认情况下,MongoDB 在 localhost 接口的端口 27017 上监听任何传入的连接。

既然数据库服务器已经启动,您可以开始使用 mongo shell 向服务器发出命令。

在查看 mongo shell 之前,让我们简单地看一下如何使用导入/导出工具将数据导入和导出 MongoDB 数据库。

首先,创建一个 CSV 文件,用以下结构保存学生的记录:

Name, Gender, Class, Score, Age.

CSV 的样本数据如图 6-1 所示。

A332296_1_En_6_Fig1_HTML.jpg

图 6-1。

Sample CSV file

接下来,将数据从 MongoDB 数据库导入到一个新的集合中,以便了解导入工具是如何工作的。

以管理员身份运行命令提示符,将其打开。以下命令用于获得关于import命令的帮助:

C:\>c:\practicalmongodb\bin\mongoimport.exe --help

Import CSV, TSV or JSON data into MongoDB.

When importing JSON documents, each document must be a separate line of the input file.

Example:

mongoimport --host myhost --db my_cms --collection docs < mydocfile.json

....

C:\>

发出以下命令,将数据从文件exporteg.csv导入到MyDB数据库中名为importeg的新集合中:

C:\>c:\practicalmongodb\bin\mongoimport.exe --host localhost --db mydb --collection importeg --type csv --file c:\exporteg.csv --headerline

2015-07-06T01:53:23.537-0700 connected to: localhost

2015-07-06T01:53:23.608-0700 imported 15 documents

为了验证集合是否被创建以及数据是否被导入,您使用 mongo shell 连接到数据库(在本例中是localhost),并发出命令来验证集合是否存在。

要启动 mongo shell,以管理员身份运行命令提示符并发出命令C:\PracticalMongoDB\bin\mongo.exe(路径会因安装文件夹而异;在这个例子中,文件夹是C:\PracticalMongoDB\),并按回车键。

默认情况下,它连接到正在监听端口 27017 的localhost数据库服务器。

C:\>c:\practicalmongodb\bin\mongo.exe

MongoDB shell version: 3.0.4

connecting to: test

> use mydb

switched to db mydb

> show collections

importeg

system.indexes

> db.importeg.find()

{ "_id" : ObjectId("5450af58c770b7161eefd31d"), "Name" : "S1", "Gender" : "M", "Class" : "C1", "Score" : 95, "Age" : 25 }

.......

{ "_id" : ObjectId("5450af59c770b7161eefd31e"), "Name" : "S2", "Gender" : "M", "Class" : "C1", "Score" : 85, "Age" : 18 }

>

简而言之,你在这里做的是

Connecting to the mongo shell   Switching to your database, which is MyDB in this case   Checking for the collections that exist in the MyDB database using show collections.   Checking the count of the collection that you imported using the import tool.   Finally, executing the find() command to check for the data in the new collection.  

要连接到不同的主机和端口,可以将–host–port与命令一起使用。

如图 6-1 所示,默认情况下,数据库test用于上下文。

在任一时间点,执行db命令将显示 shell 连接到的当前数据库:

> db

test

>

要显示所有数据库名称,可以运行show dbs命令。执行此命令将列出连接的服务器的所有数据库。

> show dbs

在任何时候,都可以使用help()命令访问帮助。

> help

db.help() help on db methods

db.mycoll.help() help on collection methods

sh.help() sharding helpers

rs.help() replica set helpers

help admin administrative help

help connect connecting to a db help

help keys key shortcuts

help misc misc things to know

help mr mapreduce

show dbs show database names

show collections show collections in current database

show users show users in current database

.............

exit quit the mongo shell

如上图所示,如果你需要关于dbcollection的任何方法的帮助,你可以使用db.help()db.<CollectionName>.help()。例如,如果你需要关于db命令的帮助,执行db.help()

> db.help()

DB methods:

db.addUser(userDocument)

...

db.shutdownServer()

db.stats()

db.version() current version of the server

>

到目前为止,您一直使用默认的test db。命令use <newdbname>可用于切换到新的数据库。

> use mydb

switched to db mydb

在开始您的探索之前,让我们先简要地看一下与 SQL 术语和概念相对应的 MongoDB 术语和概念。表 6-1 对此进行了总结。

表 6-1。

SQL and MongoDB Terminology

| 结构化查询语言 | MongoDB | | --- | --- | | 数据库ˌ资料库 | 数据库ˌ资料库 | | 桌子 | 募捐 | | 排 | 文件 | | 圆柱 | 田 | | 索引 | 索引 | | 表内联接 | 嵌入和引用 | | 主键:可以指定一列或一组列 | 主键:自动设置为 _id 字段 |

让我们开始探索 MongoDB 中的查询选项。切换到MYDBPOC数据库。

> use mydbpoc

switched to db mydbpoc

>

这将上下文从test切换到MYDBPOC。同样可以使用db命令来确认。

> db

mydbpoc

>

尽管上下文被切换到了MYDBPOC,但是如果发出了show dbs命令,数据库名称将不会出现,因为 MongoDB 直到数据被插入到数据库中时才创建数据库。这与 MongoDB 的动态数据简化方法、动态名称空间分配以及简化和加速的开发过程是一致的。如果此时发出show dbs命令,它将不会在数据库列表中列出MYDBPOC数据库,因为在数据被插入数据库之前,数据库不会被创建。

以下示例假设一个名为users的多态集合,其中包含以下两个原型的文档:

{

_id: ObjectID(),

FName: "First Name",

LName: "Last Name",

Age: 30, Gender: "M",

Country: "Country"

}

and

{

_id: ObjectID(),

Name: "Full Name",

Age: 30,

Gender: "M",

Country: "Country"

}

and

{

_id: ObjectID(), Name: "Full Name", Age: 30 }

创建和插入

现在,您将了解如何创建数据库和集合。如前所述,MongoDB 中的文档是 JSON 格式的。

首先,通过发出db命令,您将确认上下文是mydbpoc数据库。

> db

mydbpoc

>

现在您将看到如何创建文档。

第一文档符合第一原型,而第二文档符合第二原型。您已经创建了两个名为 user1 和 user2 的文档。

> user1 = {FName: "Test", LName: "User", Age:30, Gender: "M", Country: "US"}

{

"FName" : "Test",

"LName" : "User",

"Age" : 30,

"Gender" : "M",

"Country" : "US"

}

> user2 = {Name: "Test User", Age:45, Gender: "F", Country: "US"}

{ "Name" : "Test User", "Age" : 45, "Gender" : "F", "Country" : "US" }

>

接下来,您将按照以下操作顺序将这两个文档(用户 1 和用户 2)添加到users集合中:

> db.users.insert(user1)

> db.users.insert(user2)

>

上面的操作不仅将两个文档插入到users集合中,还将创建集合和数据库。同样可以使用show collectionsshow dbs命令进行验证。

如上所述,show dbs将显示数据库列表。

> show dbs

admin 0.078GB

local 0.078GB

mydb 0.078GB

mydbproc 0.078GB

并且show collections会显示当前数据库中的收藏列表。

> show collections

system.indexes

users

>

与集合users一起,system.indexes集合也被显示。这个system.indexes集合是在创建数据库时默认创建的。它管理数据库中所有集合的所有索引的信息。

执行命令db.users.find()将显示users集合中的文档。

> db.users.find()

{ "_id" : ObjectId("5450c048199484c9a4d26b0a"), "FName" : "Test", "LName" : "User", "Age" : 30, "Gender": "M", "Country" : "US" }

{ "_id" : ObjectId("5450c05d199484c9a4d26b0b"), "Name" : "Test", User", "Age" : 45,"Gender" : "F", "Country" : "US" }

>

您可以看到显示了您创建的两个文档。除了您添加到文档中的字段之外,还有一个为所有文档生成的额外的_id字段。

所有文档必须有一个唯一的 _ _id字段。如果您没有明确指定,MongoDB 会自动将它指定为惟一的对象 ID,如上例所示。

您没有明确插入一个_id字段,但是当您使用find()命令显示文档时,您可以看到一个与每个文档相关联的_id字段。

这背后的原因是默认情况下会在 _ _id字段上创建一个索引,这可以通过在system.indexes集合上发出find命令来验证。

>db.system.indexes.find()

{ "v" : 1, "key" : { "_id" : 1 }, "ns" : "mydbpoc.users", "name" : "_id_" }

>

可以使用ensureIndex()dropIndex()命令在集合中添加或删除新的索引。我们将在本章的后面讨论这一点。默认情况下,在所有集合的_id字段上创建一个索引。不能删除此默认索引。

显式创建集合

在上面的示例中,第一个插入操作隐式地创建了集合。但是,用户也可以在执行 insert 语句之前显式创建一个集合。

db.createCollection("users")

6.1.3 使用循环插入文档

还可以使用 for 循环将文档添加到集合中。下面的代码使用 for 插入用户。

> for(var i=1; i<=20; i++) db.users.insert({"Name" : "Test User" + i, "Age": 10+i, "Gender" : "F", "Country" : "India"})

>

为了验证插入是否成功,在集合上运行find命令。

> db.users.find()

{ "_id" : ObjectId("52f48cf474f8fdcfcae84f79"), "FName" : "Test", "LName" : "User", "Age" : 30, "Gender" : "M", "Country" : "US" }

{ "_id" : ObjectId("52f48cfb74f8fdcfcae84f7a"), "Name" : "Test User", "Age" : 45

, "Gender" : "F", "Country" : "US" }

................

{ "_id" : ObjectId("52f48eeb74f8fdcfcae84f8c"), "Name" : "Test User18", "Age" :

28, "Gender" : "F", "Country" : "India" }

Type "it" for more

>

用户出现在集合中。在您继续之前,让我们先理解“Type”it“for more”语句。

find命令将光标返回到结果集。光标不是在屏幕上一次显示所有文档(可能有数千或数百万个结果),而是显示前 20 个文档,并等待请求迭代(it)以显示下 20 个,依此类推,直到显示所有结果集。

也可以将结果游标赋给一个变量,然后通过编程使用 while 循环对其进行迭代。光标对象也可以作为数组来操作。

在您的情况下,如果您键入“it”并按 Enter,将出现以下内容:

> it

{ "_id" : ObjectId("52f48eeb74f8fdcfcae84f8d"), "Name" : "Test User19", "Age" :

29, "Gender" : "F", "Country" : "India" }

{ "_id" : ObjectId("52f48eeb74f8fdcfcae84f8e"), "Name" : "Test User20", "Age" :

30, "Gender" : "F", "Country" : "India" }

>

因为只剩下两个文档,所以它显示剩下的两个文档。

6.1.4 通过显式指定 _id 进行插入

在前面的 insert 示例中,没有指定_id字段,所以它是隐式添加的。在下面的例子中,您将看到在集合中插入文档时如何显式地指定_id字段。

当显式指定_id字段时,您必须记住字段的唯一性;否则插入将会失败。

以下命令明确指定了_id字段:

> db.users.insert({"_id":10, "Name": "explicit id"})

插入操作在users集合中创建以下文档:

{ "_id" : 10, "Name" : "explicit id" }

这可以通过发出以下命令来确认:

>db.users.find()

更新

在本节中,您将探索用于更新集合中文档的update()命令。

默认情况下,update()方法更新单个文档。如果需要更新所有符合选择标准的文档,可以通过将multi选项设置为 true 来完成。

让我们从更新现有列的值开始。$set操作符将用于更新记录。

以下命令将所有女性用户的国家更新为英国:

> db.users.update({"Gender":"F"}, {$set:{"Country":"UK"}})

为了检查更新是否已经发生,发出一个find命令来检查所有的女性用户。

> db.users.find({"Gender":"F"})

{ "_id" : ObjectId("52f48cfb74f8fdcfcae84f7a"), "Name" : "Test User", "Age" : 45

, "Gender" : "F", "Country" : "UK" }

{ "_id" : ObjectId("52f48eeb74f8fdcfcae84f7b"), "Name" : "Test User1", "Age" : 11, "Gender" : "F", "Country" : "India" }

{ "_id" : ObjectId("52f48eeb74f8fdcfcae84f7c"), "Name" : "Test User2", "Age" : 12, "Gender" : "F", "Country" : "India" }

...................

Type "it" for more

>

如果您检查输出,您将看到只有第一个文档记录被更新,这是 update 的默认行为,因为没有指定multi选项。

现在让我们更改update命令,并包含multi选项:

>db.users.update({"Gender":"F"},{$set:{"Country":"UK"}},{multi:true})

>

再次发出find命令,检查是否已经为所有女性员工更新了国家。发出find命令将返回以下输出:

> db.users.find({"Gender":"F"})

{ "_id" : ObjectId("52f48cfb74f8fdcfcae84f7a"), "Name" : "Test User", "Age" : 45, "Gender" : "F", "Country" : "UK" }

..............

Type "it" for more

>

如您所见,所有符合条件的记录的国家都更新为英国。

在实际的应用中工作时,您可能会遇到一种模式演变,最终可能会在文档中添加或删除字段。让我们看看如何在 MongoDB 数据库中执行这些更改。

update()操作可以在文档级使用,这有助于更新集合中的单个文档或一组文档。

接下来,让我们看看如何向文档添加新字段。为了给文档添加字段,使用带有$set操作符和multi选项的update()命令。

如果您使用不存在的带$set的字段名,那么该字段将被添加到文档中。以下命令将把字段company添加到所有文档中:

> db.users.update({},{$set:{"Company":"TestComp"}},{multi:true})

>

对用户的集合发出find命令,您会发现新字段被添加到所有文档中。

> db.users.find()

{ "Age" : 30, "Company" : "TestComp", "Country" : "US", "FName" : "Test", "Gender" : "M", "LName" : "User", "_id" : ObjectId("52f48cf474f8fdcfcae84f79") }

{ "Age" : 45, "Company" : "TestComp", "Country" : "UK", "Gender" : "F", "Name" : "Test User", "_id" : ObjectId("52f48cfb74f8fdcfcae84f7a") }

{ "Age" : 11, "Company" : "TestComp", "Country" : "UK", "Gender" : "F", ....................

Type "it" for more

>

如果使用文档中已有的字段执行update()命令,它将更新该字段的值;但是,如果文档中不存在该字段,则该字段将被添加到文档中。

接下来您将看到如何使用相同的update()命令和$unset操作符从文档中删除字段。

以下命令将从所有文档中删除字段Company:

> db.users.update({},{$unset:{"Company":""}},{multi:true})

>

这可以通过对Users集合发出find()命令来检查。您可以看到,Company字段已经从文档中删除。

> db.users.find()

{ "Age" : 30, "Country" : "US", "FName" : "Test", "Gender" : "M", "LName" : "User", "_id" : ObjectId("52f48cf474f8fdcfcae84f79") }

.............

Type "it" for more

删除

要删除集合中的文档,请使用remove ()方法。如果指定了选择标准,则只会删除符合标准的文档。如果没有指定标准,所有文档都将被删除。

以下命令将删除性别= 'M '的文档:

> db.users.remove({"Gender":"M"})

>

同样可以通过在Users上发出find()命令来验证:

> db.users.find({"Gender":"M"})

>

不返回任何文档。

以下命令将删除所有文档:

> db.users.remove({})

> db.users.find()

>

如您所见,没有文档被返回。

最后,如果您想要删除集合,以下命令将删除集合:

> db.users.drop()

true

>

为了验证集合是否被删除,发出show collections命令。

> show collections

system.indexes

>

正如您所看到的,没有显示集合名称,这表明集合已经从数据库中删除。

已经介绍了基本的创建、更新和删除操作,下一节将向您展示如何执行读操作。

阅读

在本章的这一部分,您将看到各种示例,展示作为 MongoDB 一部分的查询功能,它使您能够从数据库中读取存储的数据。

为了从基本查询开始,首先创建users集合,并按照insert命令插入数据。

> user1 = {FName: "Test", LName: "User", Age:30, Gender: "M", Country: "US"}

{

"FName" : "Test",

"LName" : "User",

"Age" : 30,

"Gender" : "M",

"Country" : "US"

}

> user2 = {Name: "Test User", Age:45, Gender: "F", Country: "US"}

{ "Name" : "Test User", "Age" : 45, "Gender" : "F", "Country" : "US" }

> db.users.insert(user1)

> db.users.insert(user2)

> for(var i=1; i<=20; i++) db.users.insert({"Name" : "Test User" + i, "Age": 10+i, "Gender" : "F", "Country" : "India"})

现在让我们从基本的查询开始。find()命令用于从数据库中检索数据。

启动一个find()命令返回集合中的所有文档。

> db.users.find()

{ "_id" : ObjectId("52f4a823958073ea07e15070"), "FName" : "Test", "LName" : "User", "Age" : 30, "Gender" : "M", "Country" : "US" }

{ "_id" : ObjectId("52f4a826958073ea07e15071"), "Name" : "Test User", "Age" : 45, "Gender" : "F", "Country" : "US" }

......

{ "_id" : ObjectId("52f4a83f958073ea07e15083"), "Name" : "Test User18", "Age" :28, "Gender" : "F", "Country" : "India" }

Type "it" for more

>

6.1.7.1 查询文档

MongoDB 提供了一个丰富的查询系统。查询文档可以作为参数传递给find()方法,以过滤集合中的文档。

查询文档在左“{”和右“}”花括号中指定。在返回结果集之前,将查询文档与集合中的所有文档进行匹配。

使用不带任何查询文档或空查询文档(如find({}))的find()命令返回集合中的所有文档。

查询文档可以包含选择器和投影仪。

选择器类似于 SQL 中的 where 条件或用于过滤结果的过滤器。

投影仪类似于用于显示数据字段的选择条件或选择列表。

6.1.7.2 选择器

现在您将看到如何使用选择器。以下命令将返回所有女性用户:

> db.users.find({"Gender":"F"})

{ "_id" : ObjectId("52f4a826958073ea07e15071"), "Name" : "Test User", "Age" : 45, "Gender" : "F", "Country" : "US" }

.............

{ "_id" : ObjectId("52f4a83f958073ea07e15084"), "Name" : "Test User19", "Age" :29, "Gender" : "F", "Country" : "India" }

Type "it" for more

>

让我们更进一步。MongoDB 还支持将不同条件合并在一起的操作符,以便根据您的需求来细化您的搜索。

让我们细化上面的查询,现在寻找来自印度的女性用户。以下命令将返回相同的结果:

> db.users.find({"Gender":"F", $or: [{"Country":"India"}]})

{ "_id" : ObjectId("52f4a83f958073ea07e15072"), "Name" : "Test User1", "Age" : 11, "Gender" : "F", "Country" : "India" }

...........

{ "_id" : ObjectId("52f4a83f958073ea07e15085"), "Name" : "Test User20", "Age" :30, "Gender" : "F", "Country" : "India" }

>

接下来,如果您想要查找属于印度或美国的所有女性用户,请执行以下命令:

>db.users.find({"Gender":"F",$or:[{"Country":"India"},{"Country":"US"}]})

{ "_id" : ObjectId("52f4a826958073ea07e15071"), "Name" : "Test User", "Age" : 45, "Gender" : "F", "Country" : "US" }

..............

{ "_id" : ObjectId("52f4a83f958073ea07e15084"), "Name" : "Test User19", "Age" :29, "Gender" : "F", "Country" : "India" }

Type "it" for more

对于聚合需求,需要使用聚合函数。接下来,您将学习如何使用count()函数进行聚合。

在上面的例子中,不是显示文档,而是想找出居住在印度或美国的女性用户的数量。因此,执行以下命令:

>db.users.find({"Gender":"F",$or:[{"Country":"India"}, {"Country":"US"}]}).count()

21

>

如果您想在不考虑任何选择器的情况下计算用户数,请执行以下命令:

> db.users.find().count()

22

>

6.1.7.3 投影仪

您已经看到了如何使用选择器过滤掉集合中的文档。在上面的例子中,find()命令返回与选择器匹配的文档的所有字段。

让我们向查询文档添加一个投影仪,除了选择器之外,您还将提到需要显示的特定细节或字段。

假设您想显示所有女性雇员的名字和年龄。在这种情况下,除了选择器,还使用了投影仪。

执行以下命令返回所需的结果集:

> db.users.find({"Gender":"F"}, {"Name":1,"Age":1})

{ "_id" : ObjectId("52f4a826958073ea07e15071"), "Name" : "Test User", "Age" : 45 }

..........

Type "it" for more

>

6.1.7.4 排序( )

在 MongoDB 中,排序顺序指定如下:1 表示升序,1 表示降序。

如果在上面的示例中,您希望按年龄的升序对记录进行排序,您可以执行以下命令:

>db.users.find({"Gender":"F"}, {"Name":1,"Age":1}).sort({"Age":1})

{ "_id" : ObjectId("52f4a83f958073ea07e15072"), "Name" : "Test User1", "Age" : 11 }

{ "_id" : ObjectId("52f4a83f958073ea07e15073"), "Name" : "Test User2", "Age" : 12 }

{ "_id" : ObjectId("52f4a83f958073ea07e15074"), "Name" : "Test User3", "Age" : 13 }

..............

{ "_id" : ObjectId("52f4a83f958073ea07e15085"), "Name" : "Test User20", "Age" :30 }

Type "it" for more

如果要按姓名降序和年龄升序显示记录,请执行以下命令:

>db.users.find({"Gender":"F"},{"Name":1,"Age":1}).sort({"Name":-1,"Age":1})

{ "_id" : ObjectId("52f4a83f958073ea07e1507a"), "Name" : "Test User9", "Age" : 19 }

............

{ "_id" : ObjectId("52f4a83f958073ea07e15072"), "Name" : "Test User1", "Age" : 11 }

Type "it" for more

6.1.7.5 极限( )

现在,您将了解如何限制结果集中的记录。例如,在拥有数千个文档的大型集合中,如果您只想返回五个匹配的文档,那么可以使用limit命令,这就使您能够做到这一点。

回到您之前对居住在印度或美国的女性用户的查询,假设您想要限制结果集,只返回两个用户。需要执行以下命令:

>db.users.find({"Gender":"F",$or:[{"Country":"India"},{"Country":"US"}]}).limit(2)

{ "_id" : ObjectId("52f4a826958073ea07e15071"), "Name" : "Test User", "Age" : 45, "Gender" : "F", "Country" : "US" }

{ "_id" : ObjectId("52f4a83f958073ea07e15072"), "Name" : "Test User1", "Age" : 11, "Gender" : "F", "Country" : "India" }

6.1.7.6·斯基普

如果要求跳过前两个记录并返回第三个和第四个用户,则使用skip命令。需要执行以下命令:

>db.users.find({"Gender":"F",$or:[{"Country":"India"}, {"Country":"US"}]}).limit(2).skip(2)

{ "_id" : ObjectId("52f4a83f958073ea07e15073"), "Name" : "Test User2", "Age" : 12, "Gender" : "F", "Country" : "India" }

{ "_id" : ObjectId("52f4a83f958073ea07e15074"), "Name" : "Test User3", "Age" : 13, "Gender" : "F", "Country" : "India" }

>

6.1.7.7 findOne()

find()类似的是findOne()命令。findOne()方法可以接受与find()相同的参数,但是它返回的不是一个光标,而是一个文档。假设您想要返回一个居住在印度或美国的女性用户。这可以使用以下命令来实现:

> db.users.findOne({"Gender":"F"}, {"Name":1,"Age":1})

{

"_id" : ObjectId("52f4a826958073ea07e15071"),

"Name" : "Test User",

"Age" : 45

}

>

类似地,如果您想在这种情况下返回第一条记录而不考虑任何选择器,您可以使用findOne(),它将返回集合中的第一个文档。

> db.users.findOne()

{

"_id" : ObjectId("52f4a823958073ea07e15070"),

"FName" : "Test",

"LName" : "User",

"Age" : 30,

"Gender" : "M",

"Country" : "US"}

使用光标的 6.1.7.8

当使用find()方法时,MongoDB 将查询结果作为游标对象返回。为了显示结果,mongo shell 对返回的光标进行迭代。

MongoDB 允许用户使用find方法的光标对象。在下一个示例中,您将看到如何将光标对象存储在一个变量中,并使用 while 循环来操作它。

假设您想要返回美国的所有用户。为了做到这一点,您创建了一个变量,将find()的输出分配给这个变量,它是一个游标,然后使用 while 循环迭代并打印输出。

代码片段如下:

> var c = db.users.find({"Country":"US"})

> while(c.hasNext()) printjson(c.next())

{

"_id" : ObjectId("52f4a823958073ea07e15070"),

"FName" : "Test",

"LName" : "User",

"Age" : 30,

"Gender" : "M",

"Country" : "US"

}

{

"_id" : ObjectId("52f4a826958073ea07e15071"),

"Name" : "Test User",

"Age" : 45,

"Gender" : "F",

"Country" : "US"

}

>

next()函数返回下一个文档。如果文档存在,hasNext()函数返回 true,并且printjson()以 JSON 格式呈现输出。

光标对象所分配到的变量也可以作为数组来操作。如果不想遍历变量,而是想在数组索引 1 处显示文档,可以运行以下命令:

> var c = db.users.find({"Country":"US"})

> printjson(c[1])

{

"_id" : ObjectId("52f4a826958073ea07e15071"),

"Name" : "Test User",

.... "Gender" : "F",

"Country" : "US"}

>

6.1.7.9 解释( )

explain()函数可用于查看 MongoDB 数据库在执行查询时正在运行哪些步骤。从 3.0 版开始,函数的输出格式和传递给函数的参数都发生了变化。它带有一个名为verbose的可选参数,该参数决定解释输出应该是什么样子。以下是详细度模式:allPlansExecutionexecutionStatsqueryPlanner。默认的详细模式是queryPlanner,这意味着如果没有指定,它默认为queryPlanner

以下代码涵盖了对username字段进行过滤时执行的步骤:

> db.users.find({"Name":"Test User"}).explain("allPlansExecution")

"queryPlanner" : {

"plannerVersion" : 1,

"namespace" : "mydbproc.users",

"indexFilterSet" : false,

"parsedQuery" : {

"$and" : [ ]

},

"winningPlan" : {

"stage" : "COLLSCAN",

"filter" : {

"$and" : [ ]

},

"direction" : "forward"

},

"rejectedPlans" : [ ]

},

"executionStats" : {

"executionSuccess" : true,

"nReturned" : 20,

"executionTimeMillis" : 0,

"totalKeysExamined" : 0,

"totalDocsExamined" : 20,

"executionStages" : {

"stage" : "COLLSCAN",

"filter" : {

"$and" : [ ]

},

"nReturned" : 20,

"executionTimeMillisEstimate" : 0,

"works" : 22,

"advanced" : 20,

"needTime" : 1,

"needFetch" : 0,

"saveState" : 0,

"restoreState" : 0,

"isEOF" : 1,

"invalidates" : 0,

"direction" : "forward",

"docsExamined" : 20

},

"allPlansExecution" : [ ]

},

"serverInfo" : {

"host" : " ANOC9",

"port" : 27017,

"version" : "3.0.4",

"gitVersion" : "534b5a3f9d10f00cd27737fbcd951032248b5952"

},

"ok" : 1

如您所见,explain()输出返回关于queryPlannerexecutionStatsserverInfo的信息。如上所述,输出返回的信息取决于所选的详细模式。

您已经看到了如何执行基本的查询、排序、限制等。您还了解了如何使用 while 循环或作为数组来操作结果集。在下一节中,您将了解索引以及如何在查询中使用它们。

使用索引

索引用于为频繁使用的查询提供高性能的读取操作。默认情况下,每当创建一个集合并将文档添加到其中时,都会在文档的_id字段上创建一个索引。

在这一节中,您将了解如何创建不同类型的索引。让我们首先使用 for 循环在一个名为testindx的新集合中插入 100 万个文档。

>for(i=0;i<1000000;i++){db.testindx.insert({"Name":"user"+i,"Age":Math.floor(Math.random()*120)})}

接下来,发出find()命令获取一个值为 user101 的名称。运行explain()命令来检查 MongoDB 正在执行哪些步骤来返回结果集。

> db.testindx.find({"Name":"user101"}).explain("allPlansExecution")

{

"queryPlanner" : {

"plannerVersion" : 1,

"namespace" : "mydbproc.testindx",

"indexFilterSet" : false,

"parsedQuery" : {

"Name" : {

"$eq" : "user101"

}

},

"winningPlan" : {

"stage" : "COLLSCAN",

"filter" : {

"Name" : {

"$eq" : "user101"

}

},

"direction" : "forward"

},

"rejectedPlans" : [ ]

},

"executionStats" : {

"executionSuccess" : true,

"nReturned" : 1,

"executionTimeMillis" : 645,

"totalKeysExamined" : 0,

"totalDocsExamined" : 1000000,

"executionStages" : {

"stage" : "COLLSCAN",

"filter" : {

"Name" : {

"$eq" : "user101"

}

},

"nReturned" : 1,

"executionTimeMillisEstimate" : 20,

"works" : 1000002,

"advanced" : 1,

"needTime" : 1000000,

"needFetch" : 0,

"saveState" : 7812,

"restoreState" : 7812,

"isEOF" : 1,

"invalidates" : 0,

"direction" : "forward",

"docsExamined" : 1000000

},

"allPlansExecution" : [ ]

},

"serverInfo" : {

"host" : " ANOC9",

"port" : 27017,

"version" : "3.0.4",

"gitVersion" : "534b5a3f9d10f00cd27737fbcd951032248b5952"

},

"ok" : 1

如您所见,数据库扫描了整个表。这会对性能产生重大影响,这是因为没有索引。

6.1.8.1 单键索引

让我们在文档的Name字段上创建一个索引。使用ensureIndex()创建索引。

> db.testindx.ensureIndex({"Name":1})

创建索引需要几分钟时间,具体取决于服务器和集合大小。

让我们运行您之前使用explain()运行的相同查询,以检查数据库在索引创建后执行的步骤。检查输出中的nnscannedmillis字段。

>db.testindx.find({"Name":"user101"}).explain("allPathsExecution")

{

"queryPlanner" : {

"plannerVersion" : 1,

"namespace" : "mydbproc.testindx",

"indexFilterSet" : false,

"parsedQuery" : {

"Name" : {

"$eq" : "user101"

}

},

"winningPlan" : {

"stage" : "FETCH",

"inputStage" : {

"stage" : "IXSCAN",

"keyPattern" : {

"Name" : 1

},

"indexName" : "Name_1",

"isMultiKey" : false,

"direction" : "forward",

"indexBounds" : {

"Name" : [

"[\"user101\", \"user101\"]"

]

}

}

},

"rejectedPlans" : [ ]

},

"executionStats" : {

"executionSuccess" : true,

"nReturned" : 1,

"executionTimeMillis" : 0,

"totalKeysExamined" : 1,

"totalDocsExamined" : 1,

"executionStages" : {

"stage" : "FETCH",

"nReturned" : 1,

"executionTimeMillisEstimate" : 0,

"works" : 2,

"advanced" : 1,

"needTime" : 0,

"needFetch" : 0,

"saveState" : 0,

"restoreState" : 0,

"isEOF" : 1,

"invalidates" : 0,

"docsExamined" : 1,

"alreadyHasObj" : 0,

"inputStage" : {

"stage" : "IXSCAN",

"nReturned" : 1,

"executionTimeMillisEstimate" : 0,

"works" : 2,

"advanced" : 1,

"needTime" : 0,

"needFetch" : 0,

"saveState" : 0,

"restoreState" : 0,

"isEOF" : 1,

"invalidates" : 0,

"keyPattern" : {

"Name" : 1

},

"indexName" : "Name_1",

"isMultiKey" : false,

"direction" : "forward",

"indexBounds" : {

"Name" : [

"[\"user101\", \"user101\"]"

]

},

"keysExamined" : 1,

"dupsTested" : 0,

"dupsDropped" : 0,

"seenInvalidated" : 0,

"matchTested" : 0

}

},

"allPlansExecution" : [ ]

},

"serverInfo" : {

"host" : "ANOC9",

"port" : 27017,

"version" : "3.0.4",

"gitVersion" : "534b5a3f9d10f00cd27737fbcd951032248b5952"

},

"ok" : 1

}

>

正如您在结果中看到的,没有表扫描。索引的创建对查询执行时间有很大的影响。

6.1.8.2 综合索引

创建索引时,您应该记住索引涵盖了您的大多数查询。如果您有时只查询Name字段,有时同时查询NameAge字段,那么在NameAge字段上创建复合索引将比在任一字段上创建索引更有益,因为复合索引将涵盖这两个查询。

下面的命令在集合testindx的字段NameAge上创建一个复合索引。

> db.testindx.ensureIndex({"Name":1, "Age": 1})

复合索引有助于 MongoDB 更高效地执行包含多个子句的查询。创建复合索引时,记住将用于精确匹配的字段(如Name : "S1")排在最前面,后面是用于范围的字段(如Age : {"$gt":20}),这一点也非常重要。

因此,上述索引将有利于以下查询:

>db.testindx.find({"Name": "user5","Age":{"$gt":25}}).explain("allPlansExecution")

{

"queryPlanner" : {

"plannerVersion" : 1,

"namespace" : "mydbproc.testindx",

"indexFilterSet" : false,

"parsedQuery" : {

"$and" : [

{

"Name" : {

"$eq" : "user5"

}

},

{

"Age" : {

"$gt" : 25

}

}

]

},

"winningPlan" : {

"stage" : "KEEP_MUTATIONS",

"inputStage" : {

"stage" : "FETCH",

"filter" : {

"Age" : {

"$gt" : 25

}

},

............................

"indexBounds" : {

"Name" : [

"[\"user5\", \"user5\"

},

"rejectedPlans" : [

{

"stage" : "FETCH",

......................................................

"indexName" : "Name_1_Age_1",

"isMultiKey" : false,

"direction" : "forward",

.....................................................

"executionStats" : {

"executionSuccess" : true,

"nReturned" : 1,

"executionTimeMillis" : 0,

"totalKeysExamined" : 1,

"totalDocsExamined" : 1,

.....................................................

"inputStage" : {

"stage" : "FETCH",

"filter" : {

"Age" : {

"$gt" : 25

}

},

"nReturned" : 1,

"executionTimeMillisEstimate" : 0,

"works" : 2,

"advanced" : 1,

"allPlansExecution" : [

{

"nReturned" : 1,

"executionTimeMillisEstimate" : 0,

"totalKeysExamined" : 1,

"totalDocsExamined" : 1,

"executionStages" : {

.............................................................

"serverInfo" : {

"host" : " ANOC9",

"port" : 27017,

"version" : "3.0.4",

"gitVersion" : "534b5a3f9d10f00cd27737fbcd951032248b5952"

},

"ok" : 1

}

>

6.1.8.3 对排序操作的支持

在 MongoDB 中,使用索引字段对文档进行排序的排序操作提供了最高的性能。

正如在其他数据库中一样,MongoDB 中的索引也因此有了顺序。如果使用索引来访问文档,它返回结果的顺序与索引的顺序相同。

对多个字段进行排序时,需要创建复合索引。在复合索引中,输出可以是索引前缀或完整索引的排序顺序。

索引前缀是复合索引的子集,复合索引包含从索引开始的一个或多个字段。

例如,以下是复合索引的索引前缀:{ x:1, y: 1, z: 1}

排序操作可以是索引前缀的任意组合,如{x: 1}, {x: 1, y: 1}

复合索引只有在作为排序的前缀时才能帮助排序。

例如,AgeNameClass,的复合索引如下

> db.testindx.ensureIndex({"Age": 1, "Name": 1, "Class": 1})

将对以下查询有用:

> db.testindx.find().sort({"Age":1})

> db.testindx.find().sort({"Age":1,"Name":1})

> db.testindx.find().sort({"Age":1,"Name":1, "Class":1})

上述索引在以下查询中不会有太大帮助:

> db.testindx.find().sort({"Gender":1, "Age":1, "Name": 1})

您可以使用explain()命令来诊断 MongoDB 是如何处理查询的。

6.1.8.4 唯一索引

在一个字段上创建索引并不能确保唯一性,所以如果在Name字段上创建索引,那么两个或更多的文档可以有相同的名称。但是,如果惟一性是需要启用的约束之一,那么在创建索引时需要将unique属性设置为 true。

首先,让我们删除现有的索引。

>db.testindx.dropIndexes()

以下命令将在testindx集合的Name字段上创建唯一索引:

> db.testindx.ensureIndex({"Name":1},{"unique":true})

现在,如果您尝试在集合中插入重复的名称,如下所示,MongoDB 将返回一个错误,并且不允许插入重复的记录:

> db.testindx.insert({"Name":"uniquename"})

> db.testindx.insert({"Name":"uniquename"})

"E11000 duplicate key error index: mydbpoc.testindx.$Name_1 dup key: { : "uniquename" }"

如果您检查集合,您会看到只存储了第一个uniquename

> db.testindx.find({"Name":"uniquename"})

{ "_id" : ObjectId("52f4b3c3958073ea07f092ca"), "Name" : "uniquename" }

>

也可以为复合索引启用唯一性,这意味着尽管单个字段可以有重复值,但组合将始终是唯一的。

例如,如果您有一个关于{"name":1, "age":1}的唯一索引,

> db.testindx.ensureIndex({"Name":1, "Age":1},{"unique":true})

>

则允许插入以下内容:

> db.testindx.insert({"Name":"usercit"})

> db.testindx.insert({"Name":"usercit", "Age":30})

然而,如果你执行

> db.testindx.insert({"Name":"usercit", "Age":30})

它会抛出一个错误,比如

E11000 duplicate key error index: mydbpoc.testindx.$Name_1_Age_1 dup key: { : "usercit", : 30.0 }

您可以先创建集合并插入文档,然后在集合上创建索引。如果在集合上创建唯一索引,而该集合在创建索引的字段中可能有重复值,则索引创建将会失败。

为了迎合这种情况,MongoDB 提供了一个dropDups选项。dropDups选项保存找到的第一个文档,并删除任何具有重复值的后续文档。

以下命令将在name字段上创建一个唯一的索引,并将删除任何重复的文档:

>db.testindx.ensureIndex({"Name":1},{"unique":true, "dropDups":true})

>

6.1.8.5 系统.索引

无论何时创建数据库,默认情况下都会创建一个system.indexes集合。关于数据库索引的所有信息都存储在system.indexes集合中。这是一个保留的集合,因此您不能修改它的文档或从中删除文档。您只能通过ensureIndexdropIndexes数据库命令来操作它。

每当创建一个索引时,可以在system.indexes中看到它的元信息。以下命令可用于获取上述集合的所有索引信息:

db.collectionName.getIndexes()

例如,以下命令将返回在testindx集合上创建的所有索引:

> db.testindx.getIndexes()

6.1.8.6 下降索引

dropIndex命令用于删除索引。

以下命令将从testindx集合中删除Name字段索引:

> db.testindx.dropIndex({"Name":1})

{ "nIndexesWas" : 3, "ok" : 1 }

>

6.1.8.7 重新索引

当您对集合执行了多次插入和删除操作后,您可能需要重新生成索引,以便能够以最佳方式使用索引。reIndex命令用于重建索引。

以下命令重建集合的所有索引。它将首先删除索引,包括_id字段上的默认索引,然后重新构建索引。

db.collectionname.reIndex()

以下命令重建testindx集合的索引:

> db.testindx.reIndex()

{

"nIndexesWas" : 2,

"msg" : "indexes dropped for collection",

"nIndexes" : 2,

..............

"ok" : 1

}

>

我们将在下一章详细讨论 MongoDB 中可用的不同类型的索引。

6.1.8.8 索引是如何工作的

MongoDB 将索引存储在 BTree 结构中,因此自动支持范围查询。

如果在一个查询中使用了多个选择标准,MongoDB 会尝试找到最佳的单个索引来选择一个候选集。之后,它依次遍历集合,以评估其他标准。

当第一次执行查询时,MongoDB 为查询可用的每个索引创建多个执行计划。它让计划在一定数量的周期内轮流执行,直到执行最快的计划完成。然后将结果返回给系统,系统会记住最快的执行计划所使用的索引。

对于后续查询,将使用记住的索引,直到集合中发生了一定数量的更新。超过更新限制后,系统将再次执行该过程,以找出当时适用的最佳索引。

当下列任一事件发生时,将重新评估查询计划:

  • 集合接收 1,000 次写操作。
  • 添加或删除了一个索引。
  • mongod 进程重启。
  • 发生了用于重建索引的重新索引。

如果想覆盖 MongoDB 的默认索引选择,同样可以使用hint()方法。

版本 2.6 中引入了索引过滤器。它由优化器将对查询进行评估的索引组成,包括查询、投影和排序。MongoDB 将使用索引过滤器提供的索引,并忽略hint()

在 2.6 版之前,MongoDB 在任何时候都只使用一个索引,所以您需要确保存在复合索引来更好地匹配您的查询。这可以通过检查查询的排序和搜索标准来完成。

索引交集是在 2.6 版中引入的。它支持索引的交集,以满足具有复合条件的查询,其中条件的一部分由一个索引满足,而另一部分由另一个索引满足。

一般来说,一个索引交集由两个索引组成;但是,可以使用多个索引交集来解析查询。这种能力提供了更好的优化。

与其他数据库一样,索引维护也有成本。每个更改集合的操作(比如创建、更新或删除)都有开销,因为索引也需要更新。为了保持最佳平衡,您需要定期检查索引的有效性,该索引可以通过您在系统上执行的读写操作的比率来衡量。找出不常用的索引并删除它们。

6.2 超越基础

本节将介绍在选择器部分使用条件操作符和正则表达式的高级查询。这些操作符和正则表达式中的每一个都为您提供了对所编写的查询的更多控制,从而也为您提供了对从 MongoDB 数据库中获取的信息的更多控制。

使用条件运算符

条件运算符使您能够更好地控制试图从数据库中提取的数据。在本节中,您将重点关注以下操作员:$lt$lte$gt$gte$in$nin$not

以下示例假设名为Students的集合包含以下类型的文档:

{

_id: ObjectID(),

Name: "Full Name",

Age: 30,

Gender: "M",

Class: "C1",

Score: 95

}

您将首先创建集合并插入几个示例文档。

>db.students.insert({Name:"S1",Age:25,Gender:"M",Class:"C1",Score:95})

>db.students.insert({Name:"S2",Age:18,Gender:"M",Class:"C1",Score:85})

>db.students.insert({Name:"S3",Age:18,Gender:"F",Class:"C1",Score:85})

>db.students.insert({Name:"S4",Age:18,Gender:"F",Class:"C1",Score:75})

>db.students.insert({Name:"S5",Age:18,Gender:"F",Class:"C2",Score:75})

>db.students.insert({Name:"S6",Age:21,Gender:"M",Class:"C2",Score:100})

>db.students.insert({Name:"S7",Age:21,Gender:"M",Class:"C2",Score:100})

>db.students.insert({Name:"S8",Age:25,Gender:"F",Class:"C2",Score:100})

>db.students.insert({Name:"S9",Age:25,Gender:"F",Class:"C2",Score:90})

>db.students.insert({Name:"S10",Age:28,Gender:"F",Class:"C3",Score:90})

> db.students.find()

{ "_id" : ObjectId("52f874faa13cd6a65998734d"), "Name" : "S1", "Age" : 25, "Gender" : "M", "Class" : "C1", "Score" : 95 }

.......................

{ "_id" : ObjectId("52f8758da13cd6a659987356"), "Name" : "S10", "Age" : 28, "Gender" : "F", "Class" : "C3", "Score" : 90 }

>

6.2.1.1 ltlt 和 lt

让我们从$lt$lte操作符开始。它们分别代表“小于”和“小于或等于”。

如果您想查找所有小于 25 岁的学生(年龄< 25),您可以使用选择器执行下面的find:

> db.students.find({"Age":{"$lt":25}})

{ "_id" : ObjectId("52f8750ca13cd6a65998734e"), "Name" : "S2", "Age" : 18, "Gender" : "M", "Class" : "C1", "Score" : 85 }

.............................

{ "_id" : ObjectId("52f87556a13cd6a659987353"), "Name" : "S7", "Age" : 21, "Gender" : "M", "Class" : "C2", "Score" : 100 }

>

如果您想找出所有大于 25 岁(年龄< = 25)的学生,请执行以下命令:

> db.students.find({"Age":{"$lte":25}})

{ "_id" : ObjectId("52f874faa13cd6a65998734d"), "Name" : "S1", "Age" : 25, "Gender" : "M", "Class" : "C1", "Score" : 95 }

....................

{ "_id" : ObjectId("52f87578a13cd6a659987355"), "Name" : "S9", "Age" : 25, "Gender" : "F", "Class" : "C2", "Score" : 90 }

>

6.2.1.2 gt gt 和gte

$gt$gte运算符分别代表“大于”和“大于或等于”。

让我们找出所有年龄大于 25 岁的学生。这可以通过执行以下命令来实现:

> db.students.find({"Age":{"$gt":25}})

{ "_id" : ObjectId("52f8758da13cd6a659987356"), "Name" : "S10", "Age" : 28, "Gender" : "F", "Class" : "C3", "Score" : 90 }

>

如果将上述示例更改为返回年龄> = 25 岁的学生,则命令为

> db.students.find({"Age":{"$gte":25}})

{ "_id" : ObjectId("52f874faa13cd6a65998734d"), "Name" : "S1", "Age" : 25, "Gender" : "M", "Class" : "C1", "Score" : 95 }

......................................

{ "_id" : ObjectId("52f8758da13cd6a659987356"), "Name" : "S10", "Age" : 28, "Gender" : "F", "Class" : "C3", "Score" : 90 }

>

6.2.1.3 in in 和nin

让我们找出属于 C1 班或 C2 班的所有学生。同样的命令是

> db.students.find({"Class":{"$in":["C1","C2"]}})

{ "_id" : ObjectId("52f874faa13cd6a65998734d"), "Name" : "S1", "Age" : 25, "Gender" : "M", "Class" : "C1", "Score" : 95 }

................................

{ "_id" : ObjectId("52f87578a13cd6a659987355"), "Name" : "S9", "Age" : 25, "Gender" : "F", "Class" : "C2", "Score" : 90 }

>

使用$nin可以返回相反的结果。

接下来让我们找出不属于 C1 或 C2 班的学生。命令是

> db.students.find({"Class":{"$nin":["C1","C2"]}})

{ "_id" : ObjectId("52f8758da13cd6a659987356"), "Name" : "S10", "Age" : 28, "Gender" : "F", "Class" : "C3", "Score" : 90 }

>

接下来让我们看看如何组合上述所有操作符并编写一个查询。假设您想找出所有性别为“M”或属于“C1”或“C2”班级且年龄大于或等于 25 岁的学生。这可以通过执行以下命令来实现:

>db.students.find({$or:[{"Gender":"M","Class":{"$in":["C1","C2"]}}], "Age":{"$gte":25}})

{ "_id" : ObjectId("52f874faa13cd6a65998734d"), "Name" : "S1", "Age" : 25, "Gender" : "M", "Class" : "C1", "Score" : 95 }

>

正则表达式

在本节中,您将了解如何使用正则表达式。正则表达式在您想要查找姓名以“A”开头的学生的情况下非常有用。

为了理解这一点,我们再补充三四个名字不同的同学。

> db.students.insert({Name:"Student1", Age:30, Gender:"M", Class: "Biology", Score:90})

> db.students.insert({Name:"Student2", Age:30, Gender:"M", Class: "Chemistry", Score:90})

> db.students.insert({Name:"Test1", Age:30, Gender:"M", Class: "Chemistry", Score:90})

> db.students.insert({Name:"Test2", Age:30, Gender:"M", Class: "Chemistry", Score:90})

> db.students.insert({Name:"Test3", Age:30, Gender:"M", Class: "Chemistry", Score:90})

>

假设您想要查找姓名以“St”或“Te”开头且班级以“Che”开头的所有学生。同样可以使用正则表达式进行过滤,如下所示:

> db.students.find({"Name":/(St|Te)*/i, "Class":/(Che)/i})

{ "_id" : ObjectId("52f89ecae451bb7a56e59086"), "Name" : "Student2", "Age" : 30, "Gender" : "M", "Class" : "Chemistry", "Score" : 90 }

.........................

{ "_id" : ObjectId("52f89f06e451bb7a56e59089"), "Name" : "Test3", "Age" : 30, "Gender" : "M", "Class" : "Chemistry", "Score" : 90 }

>

为了理解正则表达式是如何工作的,让我们以查询"Name":/(St|Te)*/i为例。

  • //i 表示正则表达式不区分大小写。
  • (St|Te)*表示名称字符串必须以“St”或“Te”开头。
  • 末尾的*表示它将匹配其后的任何内容。

当您将所有内容放在一起时,您正在对以“St”或“Te”开头的名称进行不区分大小写的匹配。在该类的正则表达式中,也发出相同的正则表达式。

接下来,让我们使查询变得复杂一点。让我们把它和上面提到的操作符结合起来。

获取姓名为 student1、student2 且年龄> =25 岁的男性学生。执行此操作的命令如下:

>db.students.find({"Name":/(student*)/i,"Age":{"$gte":25},"Gender":"M"})

{ "_id" : ObjectId("52f89eb1e451bb7a56e59085"), "Name" : "Student1", "Age" : 30,

"Gender" : "M", "Class" : "Biology", "Score" : 90 }

{ "_id" : ObjectId("52f89ecae451bb7a56e59086"), "Name" : "Student2", "Age" : 30,

"Gender" : "M", "Class" : "Chemistry", "Score" : 90 }

MapReduce

MapReduce 框架支持任务的划分,在这种情况下是跨计算机集群的数据聚合,以减少聚合数据集所需的时间。它由两部分组成:Map 和 Reduce。

这里有一个更具体的描述:MapReduce 是一个框架,用于处理高度分布在巨大数据集上并使用多个节点运行的问题。如果所有节点都有相同的硬件,这些节点统称为集群;否则,它被称为网格。这种处理可以发生在结构化数据(存储在数据库中的数据)和非结构化数据(存储在文件系统中的数据)上。

  • “Map”:在这一步中,充当主节点的节点接受输入参数,并将大问题分成多个小的子问题。这些子问题然后分布在工作节点上。工作者节点可以进一步将问题分成子问题。这导致了多级树结构。然后,工作节点将处理其中的子问题,并将答案返回给主节点。
  • “Reduce”:在这一步中,所有子问题的答案都可以通过主节点获得,然后主节点将所有答案组合起来并产生最终输出,这就是您试图解决的大问题的答案。

为了理解它是如何工作的,让我们考虑一个小例子,在这个例子中,您将找出您的集合中男女学生的数量。

这包括以下步骤:首先创建mapreduce函数,然后调用mapReduce函数并传递必要的参数。

让我们从定义map函数开始:

> var map = function(){emit(this.Gender,1);};

>

该步骤将文档作为输入,并基于Gender字段发出类型为{"F", 1}{"M", 1}的文档。

接下来,创建reduce函数:

> var reduce = function(key, value){return Array.sum(value);};

>

这将对键字段上的map函数发出的文档进行分组,在您的示例中是Gender,并将返回值的总和,在上面的示例中是“1”。上面定义的 reduce 函数的输出是基于性别的计数。

最后,使用mapReduce函数将它们放在一起,就像这样:

> db.students.mapReduce(map, reduce, {out: "mapreducecount1"})

{

"result" : "mapreducecount1",

"timeMillis" : 29,

"counts" : {

"input" : 15,

"emit" : 15,

"reduce" : 2,

"output" : 2

},

"ok" : 1,

}

>

这实际上是应用了您在students集合中定义的mapreduce函数。最终结果存储在一个名为mapreducecount1的新集合中。

为了检查它,在mapreducecount1集合上运行find()命令,如下所示:

> db.mapreducecount1.find()

{ "_id" : "F", "value" : 6 }

{ "_id" : "M", "value" : 9 }

>

这里还有一个例子来解释MapReduce的工作原理。让我们用MapReduce找出班级的平均分数。正如您在上面的例子中看到的,您需要首先创建map函数,然后创建reduce函数,最后将它们组合起来,将输出存储在数据库的一个集合中。代码片段是

> var map_1 = function(){emit(this.Class,this.Score);};

> var reduce_1 = function(key, value){return Array.avg(value);};

>db.students.mapReduce(map_1,reduce_1, {out:"MR_ClassAvg_1"})

{

"result" : "MR_ClassAvg_1",

"timeMillis" : 4,

"counts" : {

"input" : 15, "emit" : 15,

"reduce" : 3 , "output" : 5

},

"ok" : 1,

}

> db.MR_ClassAvg_1.find()

{ "_id" : "Biology", "value" : 90 }

{ "_id" : "C1", "value" : 85 }

{ "_id" : "C2", "value" : 93 }

{ "_id" : "C3", "value" : 90 }

{ "_id" : "Chemistry", "value" : 90 }

>

第一步是定义map函数,该函数循环遍历集合文档,并将输出作为{"Class": Score},返回,例如{"C1":95}。第二步是对班级进行分组,并计算该班级的平均分数。第三步合并结果;它定义了需要应用mapreduce函数的集合,最后它定义了在哪里存储输出,在本例中是一个名为MR_ClassAvg_1的新集合。

在最后一步中,使用find来检查结果输出。

6.2.4 聚合()

上一节介绍了MapReduce功能。在本节中,您将大致了解一下 MongoDB 的聚合框架。

聚合框架使您能够在不使用MapReduce函数的情况下找出聚合值。在性能方面,聚合框架比MapReduce函数更快。你需要时刻记住MapReduce是用于批量方法而不是实时分析的。

接下来,您将使用aggregate函数描述上面讨论的两个输出。首先,输出是查找男女学生的数量。这可以通过执行以下命令来实现:

> db.students.aggregate({$group:{_id:"$Gender", totalStudent: {$sum: 1}}})

{ "_id" : "F", "totalStudent" : 6 }

{ "_id" : "M", "totalStudent" : 9 }

>

类似地,为了找出类的平均分数,可以执行下面的命令:

> db.students.aggregate({$group:{_id:"$Class", AvgScore: {$avg: "$Score"}}})

{ "_id" : "Biology", "AvgScore" : 90 }

{ "_id" : "C3", "AvgScore" : 90 }

{ "_id" : "Chemistry", "AvgScore" : 90 }

{ "_id" : "C2", "AvgScore" : 93 }

{ "_id" : "C1", "AvgScore" : 85 }

>

6.3 设计应用的数据模型

在本节中,您将了解如何为应用设计数据模型。MongoDB 数据库为设计数据模型提供了两种选择:用户可以将相关对象嵌入到另一个对象中,也可以使用 ID 相互引用。在本节中,您将探索这些选项。

为了理解这些选项,您将设计一个博客应用并演示这两个选项的用法。

典型的博客应用由以下场景组成:

有人在不同的主题上发表博客。除了主题分类,还可以使用不同的标签。举例来说,如果类别是政治,帖子谈论一个政治家,那么这个政治家的名字可以作为标签添加到帖子中。这有助于用户快速找到与他们兴趣相关的帖子,并让他们将相关帖子链接在一起。

浏览博客的人可以对博客文章发表评论。

6.3.1 关系数据建模和规范化

在开始使用 MongoDB 的方法之前,让我们先绕一小段路,看看如何在关系数据库(如 SQL)中对此建模。

在关系数据库中,数据建模通常是通过定义表并逐渐删除数据冗余来实现范式的。

6.3.1.1 什么是范式?

在关系数据库中,范式通常从根据应用需求创建表开始,然后逐渐移除冗余以实现最高范式,这也被称为第三范式或 3NF。为了更好地理解这一点,让我们将博客应用数据放在表格中。初始数据如图 6-2 所示。

A332296_1_En_6_Fig2_HTML.gif

图 6-2。

Blogging application initial data

这个数据其实是第一范式的。你会有很多冗余,因为你可以对文章有多个评论,多个标签可以与文章相关联。当然,冗余的问题是它引入了不一致的可能性,即相同数据的不同副本可能具有不同的值。要消除这种冗余,您需要通过将数据拆分到多个表中来进一步规范化数据。作为此步骤的一部分,您必须标识唯一标识表中每一行的键列,以便您可以在表之间创建链接。当使用 3NF 范式建模时,上述场景将看起来像图 6-3 所示的 RDBMs 图。

A332296_1_En_6_Fig3_HTML.jpg

图 6-3。

RDBMS diagram

在这种情况下,您有一个没有冗余的数据模型,允许您更新它,而不必担心更新多行。特别是,您不再需要担心数据模型中的不一致性。

6.3.1.2 范式的问题

如前所述,标准化的好处是它允许轻松更新,没有任何冗余(即,它有助于保持数据的一致性)。更新用户名意味着更新Users表中的名称。

然而,当您试图取回数据时,问题出现了。例如,为了找到与特定用户的帖子相关联的所有标签和评论,关系数据库程序员使用一个 JOIN。通过使用连接,数据库根据应用屏幕设计返回所有数据,但是真正的问题是数据库执行什么操作来获得结果集。

一般来说,任何 RDBMS 从磁盘读取并进行寻道,这要花费 99%以上的时间来读取一行。当涉及到磁盘访问时,随机寻道是敌人。这在这个上下文中如此重要的原因是因为连接通常需要随机查找。连接操作是关系数据库中开销最大的操作之一。此外,如果最终需要将数据库扩展到多台服务器,就会引入生成分布式连接的问题,这是一个复杂且通常很慢的操作。

6.3.2 MongoDB 文档数据模型方法

如您所知,在 MongoDB 中,数据存储在文档中。幸运的是,作为应用设计者,这为模式设计带来了一些新的可能性。不幸的是,这也使我们的模式设计过程变得复杂。现在,当面临模式设计问题时,不再像关系数据库那样有固定的规范化数据库设计路径。在 MongoDB 中,模式设计取决于您试图解决的问题。

如果您必须使用 MongoDB 文档模型对上述内容进行建模,您可以将博客数据存储在一个文档中,如下所示:

{

"_id" : ObjectId("509d27069cc1ae293b36928d"),

"title" : "Sample title",

"body" : "Sample text.",

"tags" : [

"Tag1",

"Tag2",

"Tag3",

"Tag4"

],

"created_date" : ISODate("2015-07-06T12:41:39.110Z"),

"author" : "Author 1",

"category_id" : ObjectId("509d29709cc1ae293b369295"),

"comments" : [

{

"subject" : "Sample comment",

"body" : "Comment Body",

"author " : "author 2",

"created_date":ISODate("2015-07-06T13:34:23.929Z")

}

]}

如您所见,您只在一个文档中嵌入了注释和标签。或者,您可以通过引用_id字段的注释和标签来“规范化”模型:

// Authors document:

{

"_id": ObjectId("509d280e9cc1ae293b36928e "),

"name": "Author 1",}

// Tags document:

{

"_id": ObjectId("509d35349cc1ae293b369299"),

"TagName": "Tag1",.....}

// Comments document:

{

"_id": ObjectId("509d359a9cc1ae293b3692a0"),

"Author": ObjectId("508d27069cc1ae293b36928d"),

.......

"created_date" : ISODate("2015-07-06T13:34:59.336Z")

}

//Category Document

{

"_id": ObjectId("509d29709cc1ae293b369295"),

"Category": "Catgeory1"......

}

//Posts Document

{

"_id" : ObjectId("509d27069cc1ae293b36928d"),

"title" : "Sample title","body" : "Sample text.",

"tags" : [ ObjectId("509d35349cc1ae293b369299"),

ObjectId("509d35349cc1ae293b36929c")

],

"created_date" : ISODate("2015-07-06T13:41:39.110Z"),

"author_id" : ObjectId("509d280e9cc1ae293b36928e"),

"category_id" : ObjectId("509d29709cc1ae293b369295"),

"comments" : [

ObjectId("509d359a9cc1ae293b3692a0"),

]}

这一章的剩余部分将致力于确定哪种解决方案将在您的环境中工作(即是否使用引用或是否嵌入)。

6.3.2.1 嵌入

在本节中,您将看到嵌入是否会对性能产生积极的影响。当您想要获取一些数据集并将其显示在屏幕上时,例如显示与博客相关的评论的页面,嵌入会很有用;在这种情况下,评论可以嵌入到博客文档中。

这种方法的好处是,由于 MongoDB 将文档连续存储在磁盘上,所以所有相关数据都可以在一次寻道中获取。

除此之外,由于不支持连接,并且在这种情况下使用了引用,所以应用可能会执行如下操作来获取与博客相关联的评论数据。

Fetch the associated comments_id from the blogs document.   Fetch the comments document based on the comments_id found in the first step.  

如果您采用这种方法(引用),不仅数据库必须进行多次查找才能找到您的数据,而且还会在查找中引入额外的延迟,因为现在需要两次往返数据库才能检索到您的数据。

如果应用经常访问博客中的评论数据,那么几乎可以肯定的是,在博客文档中嵌入评论会对性能产生积极的影响。

支持嵌入的另一个考虑是在编写数据时对原子性和隔离性的需求。MongoDB 的设计没有多文档事务。在 MongoDB 中,操作的原子性只在单个文档级别提供,因此需要一起原子更新的数据需要一起放在单个文档中。

当您更新数据库中的数据时,您必须确保您的更新要么成功,要么完全失败,决不能有“部分成功”,并且没有其他数据库读取器会看到未完成的写操作。

6.3.2.2 参考

您已经看到,嵌入是在许多情况下提供最佳性能的方法;它还提供数据一致性保证。然而,在某些情况下,更规范化的模型在 MongoDB 中工作得更好。

拥有多个集合并添加引用的一个原因是它在查询数据时增加了灵活性。让我们用上面提到的博客例子来理解这一点。

您了解了如何使用嵌入式模式,当在一个页面上显示所有数据时(即显示博客文章和所有相关评论的页面),这种模式会工作得很好。

现在假设您需要搜索某个用户发布的评论。查询(使用这个嵌入式模式)如下所示:

db.posts.find({'comments.author': 'author2'},{'comments': 1})

那么,该查询的结果将是以下形式的文档:

{

"_id" : ObjectId("509d27069cc1ae293b36928d"),

"comments" : [ {

"subject" : "Sample Comment 1 ",

"body" : "Comment1 Body.",

"author_id" : "author2",

"created_date" : ISODate("2015-07-06T13:34:23.929Z")}...]

}

"_id" : ObjectId("509d27069cc1ae293b36928d"),

"comments" : [

{

"subject" : "Sample Comment 2",

"body" : "Comments Body.",

"author_id" : "author2",

"created_date" : ISODate("2015-07-06T13:34:23.929Z")

}...]}

这种方法的主要缺点是得到的数据比实际需要的多得多。特别是不能只要求 author2 的评论;你必须询问作者 2 评论过的帖子,这也包括对这些帖子的所有其他评论。这些数据需要在应用代码中进一步过滤。

另一方面,假设您决定使用规范化模式。在这种情况下,您将有三个文档:“作者”、“帖子”和“评论”

“作者”文档将包含特定于作者的内容,如姓名、年龄、性别等。,而“Posts”文档将包含特定于帖子的详细信息,如帖子创建时间、帖子作者、实际内容和帖子主题。

“评论”文档将包含帖子的评论,如日期时间、作者创建的评论,以及评论的文本。这描述如下:

// Authors document:

{

"_id": ObjectId("508d280e9cc1ae293b36928e "),

"name": "Jenny",

..........

}

//Posts Document

{

"_id" : ObjectId("508d27069cc1ae293b36928d"),....................

}

// Comments document:

{

"_id": ObjectId("508d359a9cc1ae293b3692a0"),

"Author": ObjectId("508d27069cc1ae293b36928d"),

"created_date" : ISODate("2015-07-06T13:34:59.336Z"),

"Post_id": ObjectId("508d27069cc1ae293b36928d"),

..........

}

在这个场景中,通过“author2”查找评论的查询可以通过对评论集合执行简单的find()来完成:

db.comments.find({"author": "author2"})

一般来说,如果您的应用的查询模式是众所周知的,并且数据往往只能以一种方式访问,那么嵌入式方法就可以很好地工作。或者,如果您的应用可能以许多不同的方式查询数据,或者您无法预测查询数据的模式,那么更“规范化”的方法可能更好。

例如,在上面的模式中,您将能够使用 limit、skip 操作符对注释进行排序或返回一组更受限制的注释。在嵌入的情况下,你不得不按照存储在文章中的顺序来检索所有的评论。

另一个可能支持使用文档引用的因素是当您有一对多关系时。

例如,一个有大量读者参与的热门博客可能会有数百甚至数千条评论。在这种情况下,嵌入会带来很大的损失:

  • 对读取性能的影响:随着文档大小的增加,它将占用更多的内存。内存的问题是,MongoDB 数据库将频繁访问的文档缓存在内存中,文档越大,放入内存的可能性就越小。这将导致在检索文档时出现更多的页面错误,这将导致随机的磁盘 I/O,从而进一步降低性能。
  • 对更新性能的影响:随着文件大小的增加以及对这类文档执行更新操作以追加数据,MongoDB 最终需要将文档移动到一个有更多可用空间的区域。这种移动一旦发生,会显著降低更新性能。

除此之外,MongoDB 文档有一个 16MB 的硬性大小限制。尽管需要注意这一点,但在达到 16MB 的大小限制之前,您通常会由于内存压力和文档复制而遇到问题。

支持使用文档引用的最后一个因素是多对多或 M:N 关系。

例如,在上面的例子中,有一些标签。每个博客可以有多个标签,每个标签可以与多个博客条目相关联。

实现 blogs-tags M:N 关系的一种方法是拥有以下三个集合:

  • Tags集合,它将存储标签的详细信息
  • Blogs集合,其中将有博客的详细信息
  • 第三个集合叫做Tag-To-Blog Mapping,它将在标签和博客之间进行映射

这种方法类似于关系数据库中的方法,但是这会对应用的性能产生负面影响,因为查询最终会执行大量应用级的“连接”

或者,您可以使用嵌入模型,在博客文档中嵌入标签,但是这将导致数据重复。虽然这将稍微简化读取操作,但它将增加更新操作的复杂性,因为在更新标签细节时,用户需要确保更新的标签在嵌入到其他博客文档中的每个地方都得到更新。

因此,对于多对多连接,折衷的方法通常是最好的,嵌入一列_id值而不是整个文档:

// Tags document:

{

"_id": ObjectId("508d35349cc1ae293b369299"),

"TagName": "Tag1",

..........

}

// Posts document with Tag IDs added as References

//Posts Document

{ "_id" : ObjectId("508d27069cc1ae293b36928d"),

"tags" : [

ObjectId("509d35349cc1ae293b369299"),

ObjectId("509d35349cc1ae293b36929a"),

ObjectId("509d35349cc1ae293b36929b"),

ObjectId("509d35349cc1ae293b36929c")

],....................................

}

尽管查询会有点复杂,但您不再需要担心到处更新标签。

总之,MongoDB 中的模式设计是您需要做出的最早决策之一,它取决于应用的需求和查询。

正如您所看到的,当您需要一起访问数据或者需要进行原子更新时,嵌入将会产生积极的影响。但是,如果您在查询时需要更多的灵活性,或者如果您有多对多的关系,使用引用是一个不错的选择。

最终,决定取决于应用的访问模式,在 MongoDB 中没有严格的规则。在下一节中,您将了解各种数据建模注意事项。

数据建模的 6.3.2.3 决策

这包括决定如何组织文档,以便有效地对数据建模。需要决定的重要一点是,您是需要嵌入数据还是使用对数据的引用(即,是使用嵌入还是引用)。

这一点最好用一个例子来说明。假设你有一个书评网站,里面有作者和书籍,还有带线索评论的评论。

现在的问题是如何组织收藏。这个决定取决于每本书预期的注释数量以及执行读写操作的频率。

6.3.2.4 操作注意事项

除了元素之间的交互方式(即是否以嵌入式方式存储文档或使用引用),在为应用设计数据模型时,许多其他操作因素也很重要。这些因素将在以下章节中介绍。

数据生命周期管理

如果您的应用有只需要在数据库中保留有限时间的数据集,则需要使用此功能。

比如说你需要保留一个月的评审和评论的相关数据。可以考虑这个特点。

这是通过使用集合的生存时间(TTL)功能实现的。集合的 TTL 功能确保文档在一段时间后过期。

此外,如果应用要求只处理最近插入的文档,那么使用上限集合将有助于优化性能。

索引

可以创建索引来支持常用的查询,以提高性能。默认情况下,MongoDB 会在_id字段上创建一个索引。

以下是创建索引时需要考虑的几点:

  • 每个索引至少需要 8KB 的数据空间。
  • 对于写操作,添加索引会对性能产生一些负面影响。因此,对于具有大量写入的集合,索引可能很昂贵,因为对于每次插入,必须将键添加到所有索引中。
  • 索引对于具有大量读取操作的集合非常有用,例如读写操作比例很高的情况。未索引的读取操作不受索引的影响。
分片

设计应用模型时的一个重要因素是是否对数据进行分区。这是在 MongoDB 中使用分片实现的。

分片也称为数据分区。在 MongoDB 中,集合被分区,其文档分布在机器集群上,这些机器集群被称为碎片。这可能会对性能产生重大影响。我们将在 tk 章节中更多地讨论分片。

大量的收藏

拥有多个集合与将数据存储在单个集合中的设计考虑事项如下:

  • 选择多个集合来存储数据不会影响性能。
  • 对于不同类型的数据拥有不同的集合可以提高高吞吐量批处理应用的性能。

当您设计具有大量集合的模型时,需要考虑以下行为:

  • 几千字节的某个最小开销与每个集合相关联。
  • 每个索引至少需要 8KB 的数据空间,包括_id索引。

现在您已经知道每个数据库的元数据都存储在<database>.ns文件中。每个集合和索引在名称空间文件中都有自己的条目,因此在决定实现大量集合时,需要考虑名称空间文件的大小限制。

文档的增长

一些更新,比如将一个元素推送到一个数组,添加新的字段等等。会导致文档尺寸的增加,这可能会导致文档从一个槽移动到另一个槽以适合文档。这一文档重定位过程既耗费资源又耗费时间。尽管 MongoDB 提供了填充来最小化重定位的发生,但是您可能需要手动处理文档的增长。

6.4 总结

在本章中,您学习了基本的 CRUD 操作和高级查询功能。您还研究了存储和检索数据的两种方式:嵌入和引用。

在下一章中,您将了解 MongoDB 架构、其核心组件和特性。