跳至主要内容

MongoDB 教程:如何设置并查询 MongoDB 数据库

学习如何在 Python 中连接并分析 MongoDB 数据库。
· 10分钟 阅读

使用 AI 探索

您可能听说过关系型数据库,甚至用过。行-表的格式是存储信息最流行、最直观的结构。不幸的是,您无法把遇到的所有数据都装进行和表里。事实上,现实世界中有太多问题需要用到非关系型数据库。那么,有替代方案吗?

答案是肯定的!有四种不使用行或表的数据库。因为不能用 SQL 来查询它们,所以被称为 NoSQL 数据库。它们包括:

  • 键值数据库
  • 文档型数据库
  • 列族数据库
  • 图数据库

本文聚焦于文档型数据库,以及如何使用名为 MongoDB 的服务器。但在进入技术细节之前,让我们先看看文档型数据库的使用场景。关于图数据库的更多信息,您可以查看我们单独的图数据库指南。

何时使用文档型数据库?

选择文档型数据库的一个主要场景是:当您的数据无法整齐地适配到像表这样的预定义模式中。许多行业中的流程或应用程序会存储这类数据。以下是一些例子:

  • Web 与移动应用:用户画像、偏好、内容与互动
  • 内容管理系统:存储更广泛的媒体类型,如文本、图像、视频、GIF 等
  • 电商平台:产品目录、客户信息、订单历史、库存等
  • 游戏:存储玩家档案、排行榜名次
  • 日志与数据采集:用于分析的大量日志、事件、指标等

想一想这些行业收集的数据如何装进表格里。比如,电商平台将产品目录存入预定义模式会非常困难。不同产品有不同属性,或者更糟,有不同数量的属性。对于 100 个不同品牌的无人机,您需要 10 列来存 10 个物理属性,还是只需要 5–6 列来存书籍信息?

在这种场景下,基于表的数据库帮不上忙。使用 MongoDB 等文档型数据库,您将获得以下好处:

  • 无需前期为设计模式付出开发成本
  • 文档(数据)可随时间变化(包括数据类型、属性数量等)
  • 文档型数据库避免了连接操作,从而使查询更快
  • 对开发者直观,因为文档型数据库大多是大型 JSON 文件,对 Python 使用者而言基本上就是超大的字典
  • 文档数据库横向扩展,即随着数据库增长不需要成比例增加单机计算资源

现在,让我们来看看围绕文档型数据库和 MongoDB 的核心概念。

MongoDB 的核心概念

到目前为止我一再提到“文档型数据库”,但它们究竟是什么?以下是主要概念:

  • 文档(Documents):数据存储在称为文档的对象中。简单说,文档类似于 JSON 键值对象。单个文档等同于表中的一行。如果您是 Python 程序员,可以把文档理解为字典即可。只需记住,文档可以包含嵌套文档——这是文档型数据库的一大特性。
  • 集合(Collections):集合就像关系型数据库中的表,但它包含的不是行,而是文档。集合是庞大的数据结构,可能包含成千上万甚至数百万个字典。
  • 无模式(Schema-less):精彩之处在于此。一个集合可以包含大小不一的文档。例如,文档 1 可以有 10 个键值对,而文档 2 可以有 15 个(只要信息属于同一类别/主题,如产品,集合就有意义)。

下面这张优秀的表格总结了关系型数据库与文档型数据库的区别:

特性文档型数据库(如 MongoDB)关系型数据库(如 MySQL、PostgreSQL)
数据结构以文档形式存储数据(如 JSON、BSON),允许灵活的分层结构。以具有行和列的表格形式存储数据,遵循预定义的模式。
模式灵活性无模式:文档可以有不同结构,允许不同字段和数据类型。固定模式:需要预定义的模式,包含特定列和数据类型。
查询语言使用 MongoDB 查询语言(MQL)或类似语言,基于对象、更为灵活。使用 SQL(结构化查询语言)来查询结构化数据。
连接(Joins)通过在文档内嵌相关数据来避免连接(反规范化)。支持跨表的复杂连接(规范化)。
性能对非结构化或半结构化数据的读写更快,避免了连接带来的开销。在结构化数据上性能强劲,但连接可能会拖慢查询。
可扩展性横向可扩展:可通过分片将数据分布到多台服务器。通常纵向扩展:依赖更强大的硬件,不过部分也支持横向扩展(如分区)。
事务支持支持多文档 ACID 事务(自 MongoDB 4.0 起),但最初设计用于非事务性操作。全面支持符合 ACID 的事务,提供强一致性与可靠性。
使用场景最适合用户画像、日志、目录及灵活数据结构等非结构化或半结构化数据。理想于具有清晰关系的结构化数据,如财务记录或 ERP。
数据关系支持嵌入式数据(反规范化),便于一次查询即可检索相关信息。依赖外键在表间建立关系(规范化)。
索引支持索引,但种类与复杂度不如关系型数据库丰富。强大的索引能力,支持多种索引类型(如 B 树、哈希)以优化性能。
一致性在分布式环境中提供最终一致性,但也可在需要时提供强一致性(通过 ACID 事务)。由于 ACID 事务与关系完整性,多数情况下确保强一致性。
数据量扩展通过增加服务器(分片)即可轻松扩展以容纳海量数据。可纵向扩展,但横向扩展需要更复杂的配置(如分区)。
数据完整性数据完整性在每个文档内部管理,但跨文档的关系管理更具挑战。通过主外键以及 UNIQUE、NOT NULL 等约束,内建支持强数据完整性。
对开发者友好对开发者友好:数据建模灵活,与现代应用(如 JSON、REST API)契合良好。数据建模更为刚性,但熟悉 SQL 与结构化数据的开发者易于理解。

现在,让我们真正开始在 MongoDB 中操作文档吧!

MongoDB 安装与设置:连接数据源

要查询文档型数据库,我们需要安装 MongoDB 服务器。以下是按平台划分的说明:

  • Windows 用户请按照此链接的说明进行操作。
  • 类 Unix 系统可在终端中安装 MongoDB:
$ sudo apt-get install -y mongodb

然后,在虚拟环境中安装 pymongo 和 requests 库。pymongo 是 MongoDB 服务器的官方 Python 适配器。我们还需要 requests 库从 API 拉取数据。

$ pip install pymongo
$ pip install requests

接着,在终端中使用以下命令启动 MongoDB 服务器:

$ sudo service mongodb start

现在,我们可以把一些数据加载到文档型数据库中。通常有两种情形:

  1. 您在本地已有合适格式的数据,如 JSON、BSON、YAML 或 XML。
  2. 您需要从外部来源(通常是 API)拉取数据。

我们将两种都介绍。首先,在本地加载名为 drone_races.json 的集合。代码片段如下:

import json

from pymongo import MongoClient

# Establish connection to MongoDB
client = MongoClient("localhost", 27017)
# Create a database named "drones"
drones = client["drones"]
# Create a collection named "races"
races = drones["races"]

# Load dataset into MongoDB
with open("data/drone_races.json", "r") as file:
   data = json.load(file)
   races.insert_many(data)

对我们来说最重要的两个对象是 drones(数据库)和 races(集合)。大多数函数与方法都与集合相关。数据库对象主要用于管理集合。

现在,让我们看看如何通过 API 加载同样的数据。我使用名为 Mockaroo 的服务将这些信息发布为 API。代码如下:

import requests
from pymongo import MongoClient

# Fetch data from the API
api_url = (
   "https://my.api.mockaroo.com/drone_race_matches.json?key=6f5a6b50"
)
response = requests.get(api_url)

if response.status_code == 200:
   data = response.json()  # Get the JSON data from the API

   # Establish a connection to MongoDB
   client = MongoClient()

   # Access or create a specific database
   drones = client["drones"]

   # Access or create a specific collection within the database
   races = drones["races"]

   # Insert the fetched data into the MongoDB collection
   races.insert_many(data)
else:
   print("Failed to fetch data from the API.")

我们已经将一些数据加载到了文档数据库 drones 的 races 集合中,还是说并没有?让我们用查询来检查一下!

基础 MongoDB 查询

在 MongoDB 中统计文档数量

要判断集合中是否存在数据,我们需要统计其中的文档数量。我们将使用 count_documents 方法:

>>> races.count_documents({})
9040

注意传给 count_documents 的空字典。这个字典在 MongoDB 中称为过滤器。随着教程推进,我们将学习如何填写这个字典来创建不同类型的过滤器。现在,我们不使用过滤器。上述代码等价于 SQL 中的 SELECT COUNT(*) FROM table_name。

我们得到了 9040 个文档——很好!现在来看一些数据。

在 MongoDB 中提取一个文档

要使用 pymongo 查看一个文档,我们可以使用 find_one 方法:

from pprint import pprint

>>> pprint(races.find_one())
{'_id': ObjectId('659d31e9255ec0cf4bab529d'),
'laps': 3,
'league': 'F1 Drones',
'location': {'city': 'Ford',
             'country': 'United Kingdom',
             'date': 'error: invalid date "2024-10-25"',
             'venue': 'Manhattan Seas'},
'name': 'Honorable',
'pilots': {'drone': 'DJI3-old',
           'finishing_position': 66,
           'name': 'Kariotta Cow',
           'qualification_time': 27.39,
           'team': 'Sky Crusaders',
           'telemetry': {'altitude': 34.3,
                         'battery_voltage': 12.1,
                         'speed': 68.3,
                         'timestamp': 'error: invalid date '
                                      '"2024-10-25T14:09:26Z"'}},
'sponsors': ['Fat Shark', 'DJI', 'Etisalat'],
'weather_conditions': 'snowy'}

留意该文档的字段(键)。它存储了单场无人机比赛的信息,包括:

  • 圈数
  • 比赛时的天气状况
  • 比赛期间的飞手(非全部)
  • 比赛赞助商等

文档还有一个必需的 _id 字段,即唯一哈希值。

在 MongoDB 中选择所有文档

count_documents 总是返回一个数字,但有时我们希望查看查询匹配到的数据。为此,我们可以使用 find_one 的“老大哥”——find:

from pprint import pprint

for race in races.find():
   pprint(race)
   break

{'_id': ObjectId('659d31e9255ec0cf4bab529d'),
'laps': 3,
'league': 'F1 Drones',
'location': {'city': 'Ford',
             'country': 'United Kingdom',
             'date': 'error: invalid date "2024-10-25"',
             'venue': 'Manhattan Seas'},
'name': 'Honorable',
'pilots': {'drone': 'DJI3-old',
           'finishing_position': 66,
           'name': 'Kariotta Cow',
           'qualification_time': 27.39,
           'team': 'Sky Crusaders',
           'telemetry': {'altitude': 34.3,
                         'battery_voltage': 12.1,
                         'speed': 68.3,
                         'timestamp': 'error: invalid date '
                                      '"2024-10-25T14:09:26Z"'}},
'sponsors': ['Fat Shark', 'DJI', 'Etisalat'],
'weather_conditions': 'snowy'}

find 在空查询(无参数)时会一个接一个返回文档,但这并不是我们想要的!我们希望执行查询来回答关于数据的有趣问题。这正是过滤器文档大显身手之处。

在 MongoDB 中基于条件选择

让我们从最简单的过滤器开始——匹配某个字段等于某个值的文档。这相当于:

SELECT *
 FROM table_name
WHERE field = value

我们在 MongoDB 中这样做:

criteria = {"sponsors": "Fat Shark"}

fat_shark_races = races.count_documents(criteria)
fat_shark_races

6194

上面我们选择了赞助商为 "Fat Shark" 的比赛。其语法就是一个字典,将 sponsors 字段映射到 "Fat Shark"。

如果没有一些常见的不等式运算符,MongoDB 查询语言也就称不上语言了。以下是如何使用“小于”运算符:

criteria = {"pilots.qualification_time": {"$lt": 10}}

quick_races = races.count_documents(criteria)
quick_races

3061

上述查询引入了 MongoDB 查询语言(MQL)的四个新特性:

  • 可以使用点号表示法访问子字段。pilots.qualification_time 提取的是 pilots 字段下嵌套的资格赛时间。
  • MQL 中几乎所有运算符都以美元符开头。
  • 运算符在像上面那样的嵌套文档中使用。
  • $lt 表示“小于”运算符。

因此,结果表明有 3061 场比赛中至少有一位飞手的资格赛用时少于 10 秒。该查询得益于 $lt 运算符。它的“兄弟姐妹”包括:

  • $lte:小于等于
  • $gt:大于
  • $gte:大于等于

它们与 $lt 的语法相同。

在 MongoDB 中使用逻辑条件运算符进行选择

MQL 也包含逻辑条件运算符,如 $and 和 $or。我们从后者开始。

我们将检索地点为英国或赞助商为 Etisalat 的比赛:

criteria = {
   "$or": [
       {"location.country": "United Kingdom"},
       {"sponsors": "Etisalat"},
   ]
}

>>> races.count_documents(criteria)

6223

同样,使用 Explain code 按钮可查看详细解释。

共有 6223 个文档匹配我们的条件。若要对同一字段的多个值使用 OR 逻辑,我们可以使用 $in 运算符。

例如,可以这样检查恶劣天气状况:

criteria = {
   "weather_conditions": {"$in": ["rainy", "snowy", "cloudy"]}
}

>>> races.count_documents(criteria)

5508

若用 $or 运算符来写,这个查询会很麻烦。现在来说说 $and。

这次我们想找地点在澳大利亚且赞助商为 Fat Shark 的比赛。使用 $and 的方法如下:

criteria = {
   "$and": [
       {"location.country": "Australia"},
       {"sponsors": "Fat Shark"},
   ]
}
>>> races.count_documents(criteria)
193

但在实践中,您很少会使用 $and,因为可以用更简单的方式实现:

criteria = {
   "location.country": "Australia",
   "sponsors": "Fat Shark",
}

races.count_documents(criteria)
193

只需在过滤器文档中添加更多键值对即可实现 AND 逻辑运算。

最后,还有 $nin 运算符,用于检查不属于集合的情况。比如,我们可以返回所有不在美国、英国或澳大利亚举办的比赛:

criteria = {
   "location.country": {
       "$nin": ["United States", "United Kingdom", "Australia"]
   }
}

>>> races.count_documents(criteria)
126

这样就只剩下阿拉伯联合酋长国这个国家,因此上述查询其实可以写成:

criteria = {"location.country": "United Arab Emirates"}

>>> races.count_documents(criteria)
126

不过,您已经明白意思了。

在 MongoDB 中查询空值或缺失值

检查空值或缺失值是所有数据分析任务中的通用操作。因此,MongoDB 提供了一个相应的运算符——$exists。以下是两个检查某字段是否存在的示例:

criteria = {"location.district": {"$exists": True}}

>>> races.count_documents(criteria)

0

嗯,看来 district 字段在任何文档中都不存在。另一方面,laps 字段在所有文档中都必须存在,因为它是比赛的关键信息。

criteria = {"laps": {"$exists": True}}

races.count_documents(criteria)
9040

如预期,所有文档都有 laps 字段。但如果字段存在却为 null 呢?我们也可以检查:

criteria = {"pilots.finishing_position": None}

races.count_documents(criteria)
0

通过使用 Python 内置的 None 对象,我们可以检查任意字段的缺失情况。

还有一些需要空值或存在性检查的高级场景。例如,您可能想检查某个大型嵌套数组的特定元素是否存在。

为此,我们可以在 MQL 中使用数组索引语法。例如,要找到只有一个赞助商的比赛,我们需要检查 sponsors 数组的第二个元素是否存在:

# Counting starts with 0 as always
criteria = {"sponsors.1": {"$exists": False}}

races.count_documents(criteria)
2929

只需在键后附加元素的索引号即可实现。因此,在我们的集合中,近 3000 场比赛仅由一个实体赞助。

这种数组索引语法适用于许多其他运算符,不仅限于 $exists。

投影(限制返回字段)

本教程要讲的最后一件事是投影。到目前为止,我们的查询结果包含每个文档中的所有字段。当文档有上百个字段时,这并不理想。想象一下打印输出时的“伤眼”程度!

因此,为了选择我们希望返回的字段,我们可以使用投影。方法如下:

criteria = {"pilots.telemetry.speed": {"$gte": 20}}
projection = {
   "sponsors": 1,
   "location.country": 1,
   "pilots.telemetry.speed": 1,
   "pilots.name": 1,
}

fast_pilots = races.find(criteria, projection)

for pilot in fast_pilots:
   pprint(pilot)
   break

在上述示例中,我们照常编写过滤条件,但这次我们定义了另一个只有四个字段设为 1 的文档。如果将该 projection 文档作为第二个参数传给 find 或 count_documents,输出中将只返回设为 1 的字段。

{'_id': ObjectId('659d31e9255ec0cf4bab529d'),
'location': {'country': 'United Kingdom'},
'pilots': {'name': 'Kariotta Cow', 'telemetry': {'speed': 68.3}},
'sponsors': ['Fat Shark', 'DJI', 'Etisalat']}

尽管我们只选择了四个字段,但讨厌的 _id 字段还是“挤”了进来。若要抑制这种行为,请在 projection 字典中将其设为 0:

criteria = {"pilots.telemetry.speed": {"$gte": 20}}
projection = {
   "sponsors": 1,
   "location.country": 1,
   "pilots.telemetry.speed": 1,
   "pilots.name": 1,
   "_id": 0,
}

fast_pilots = races.find(criteria, projection)

for pilot in fast_pilots:
   pprint(pilot)
   break

{'location': {'country': 'United Kingdom'},
'pilots': {'name': 'Kariotta Cow', 'telemetry': {'speed': 68.3}},
'sponsors': ['Fat Shark', 'DJI', 'Etisalat']}

现在,漂亮多了。

最后,若想返回除少数字段外的所有字段,可以将这些字段设为 0:

projection = {"_id": 0, "league": 0, "pilots": 0}

# Empty criteria for this one
races.find_one({}, projection)

{'name': 'Honorable',
'location': {'venue': 'Manhattan Seas',
 'city': 'Ford',
 'country': 'United Kingdom',
 'date': 'error: invalid date "2024-10-25"'},
'sponsors': ['Fat Shark', 'DJI', 'Etisalat'],
'laps': 3,
'weather_conditions': 'snowy'}

如您所见,这次我们得到了除 _id、league 和 pilots 外的所有字段。

结语

作为一款数据库管理工具,MongoDB 的体量远超本教程所能覆盖。今天,我们只介绍了 GET 查询(用于检索信息的查询),但 MongoDB 也允许数据专业人士在文档数据库中插入、更新或删除信息。我们还略过了一整类查询——聚合。

所有这些主题都超出了本文范围,学习它们需要额外资源。不妨看看这些:

FAQs

文档型数据库(如 MongoDB)与关系型数据库有何不同?

文档型数据库(如 MongoDB)将数据存储在文档中(通常是类 JSON 的格式),可以包含嵌套的数据结构。这与关系型数据库不同,后者在具有固定模式的行和表中存储数据。文档型数据库更灵活,因为其模式是动态的,意味着每个文档都可以拥有不同的字段和数据类型。这使得 MongoDB 适用于非结构化或半结构化数据,而关系型数据库需要预定义的模式。

为什么要用 MongoDB 而不是关系型数据库?

当处理无法整齐适配表格结构的数据时,MongoDB 会很有用。如果您的数据具有灵活模式,预期数据结构会频繁变化,或需要处理大量非结构化数据,请使用 MongoDB。对于需要在规模化场景下进行高速读写的应用(如电商、日志记录、内容管理系统),MongoDB 也是不错的选择。

哪些编程语言与 MongoDB 兼容?

MongoDB 与多种编程语言兼容,包括通过官方驱动与库支持的 Python、Java、JavaScript、Node.js、Go、Ruby 和 C#。在数据科学应用中,常用的 Python 库是 pymongo。MongoDB 也能很好地与 Django、Flask 和 Express.js 等现代框架集成。

MongoDB 如何处理大规模数据与横向扩展?

MongoDB 通过分片实现横向扩展,将数据分布到多台服务器,以高效管理大规模数据。随着数据增长,MongoDB 可以把负载分散到多台机器上,从而提升性能与容量。这使 MongoDB 非常适合大数据应用或数据量快速增长的场景。

MongoDB 能像 SQL 数据库一样处理复杂查询吗?

是的,MongoDB 可以处理复杂查询,但其查询语言(MQL,MongoDB Query Language)与 SQL 相当不同。MongoDB 支持过滤、投影、逻辑运算以及聚合等功能来执行复杂查询,允许您检索、过滤与转换数据。不过,与 SQL 数据库不同,MongoDB 不以相同方式支持连接(joins),因为它旨在将数据以灵活的文档结构进行反规范化存储。

MongoDB 是否适合实时分析?

MongoDB 可用于实时分析,但性能很大程度取决于数据的结构与索引情况。借助 MongoDB 强大的索引与聚合框架,您可以高效运行实时查询并生成洞见。然而,对于更复杂的分析任务,您可以考虑将 MongoDB 与 Apache Spark 等工具集成,或使用其聚合框架来处理大规模的实时计算。

MongoDB 拥有哪些安全特性?

MongoDB 提供多种安全特性,包括身份验证、授权(基于角色的访问控制)、加密(传输中与静态数据)以及审计。MongoDB 的企业版还提供 LDAP 集成和Kerberos 身份验证等企业级安全特性。这些功能有助于保护敏感数据,同时保持对行业法规的合规性。

MongoDB 能处理 ACID 事务吗?

是的,自 4.0 版本起,MongoDB 支持符合 ACID 的事务。这使得与关系型数据库类似的多文档事务成为可能,确保涉及多个文档或集合的操作具有原子性、一致性、隔离性和持久性。这使 MongoDB 更适合需要事务保证的场景。

在 MongoDB 中,JSON 与 BSON 有何区别?

JSON 是一种常用的人类可读的数据表示格式,而 BSON(二进制 JSON)是 MongoDB 的存储格式。BSON 允许更高效地存储与检索数据,并支持诸如日期与二进制数据等 JSON 原生不支持的额外数据类型。BSON 还增加了更多元数据,从而提升文档存储与检索时的性能。

Bexruz (Bex) Tuychiev

作者

Bexruz (Bex) Tuychiev

我是一名数据科学内容创作者,拥有超过 2 年的经验,并在 Medium 上拥有较大的读者群。我喜欢用略带讽刺的笔调撰写有关 AI 和机器学习的深度文章——毕竟总得想点办法让这些内容不那么枯燥。我已发表 130 多篇文章,并制作了一门 DataCamp 课程,另有一门正在筹备中。我的内容已被超过 500 万人次阅读,其中有 2 万人在 Medium 和 LinkedIn 上成为关注者。 

主题