MongoDB 的核心概念
到目前为止我一再提到“文档型数据库”,但它们究竟是什么?以下是主要概念:
- 文档(Documents):数据存储在称为文档的对象中。简单说,文档类似于 JSON 键值对象。单个文档等同于表中的一行。如果您是 Python 程序员,可以把文档理解为字典即可。只需记住,文档可以包含嵌套文档——这是文档型数据库的一大特性。
- 集合(Collections):集合就像关系型数据库中的表,但它包含的不是行,而是文档。集合是庞大的数据结构,可能包含成千上万甚至数百万个字典。
- 无模式(Schema-less):精彩之处在于此。一个集合可以包含大小不一的文档。例如,文档 1 可以有 10 个键值对,而文档 2 可以有 15 个(只要信息属于同一类别/主题,如产品,集合就有意义)。
下面这张优秀的表格总结了关系型数据库与文档型数据库的区别:
现在,让我们真正开始在 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
现在,我们可以把一些数据加载到文档型数据库中。通常有两种情形:
- 您在本地已有合适格式的数据,如 JSON、BSON、YAML 或 XML。
- 您需要从外部来源(通常是 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 也允许数据专业人士在文档数据库中插入、更新或删除信息。我们还略过了一整类查询——聚合。
所有这些主题都超出了本文范围,学习它们需要额外资源。不妨看看这些:
- 使用 Python 在数据科学中引入 MongoDB 的课程——一门综合课程,也涵盖聚合等高级查询
- Python 中的 MongoDB 入门——另一篇 MongoDB 教程,涵盖使用 pymongo 进行 CRUD(创建、读取、更新与删除)操作
- NoSQL 概念课程——如果您对 NoSQL 数据库感兴趣,这门课程适合您。除文档型数据库外,还涵盖其他非关系型数据库。
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 还增加了更多元数据,从而提升文档存储与检索时的性能。

我是一名数据科学内容创作者,拥有超过 2 年的经验,并在 Medium 上拥有较大的读者群。我喜欢用略带讽刺的笔调撰写有关 AI 和机器学习的深度文章——毕竟总得想点办法让这些内容不那么枯燥。我已发表 130 多篇文章,并制作了一门 DataCamp 课程,另有一门正在筹备中。我的内容已被超过 500 万人次阅读,其中有 2 万人在 Medium 和 LinkedIn 上成为关注者。
您可能听说过关系型数据库,甚至用过。行-表的格式是存储信息最流行、最直观的结构。不幸的是,您无法把遇到的所有数据都装进行和表里。事实上,现实世界中有太多问题需要用到非关系型数据库。那么,有替代方案吗?
答案是肯定的!有四种不使用行或表的数据库。因为不能用 SQL 来查询它们,所以被称为 NoSQL 数据库。它们包括:
本文聚焦于文档型数据库,以及如何使用名为 MongoDB 的服务器。但在进入技术细节之前,让我们先看看文档型数据库的使用场景。关于图数据库的更多信息,您可以查看我们单独的图数据库指南。
何时使用文档型数据库?
选择文档型数据库的一个主要场景是:当您的数据无法整齐地适配到像表这样的预定义模式中。许多行业中的流程或应用程序会存储这类数据。以下是一些例子:
想一想这些行业收集的数据如何装进表格里。比如,电商平台将产品目录存入预定义模式会非常困难。不同产品有不同属性,或者更糟,有不同数量的属性。对于 100 个不同品牌的无人机,您需要 10 列来存 10 个物理属性,还是只需要 5–6 列来存书籍信息?
在这种场景下,基于表的数据库帮不上忙。使用 MongoDB 等文档型数据库,您将获得以下好处:
现在,让我们来看看围绕文档型数据库和 MongoDB 的核心概念。