一篇搞懂 MongoDB:入门教程
随着互联网应用的爆发式增长,数据呈现出海量化、多样化的特点。传统的关系型数据库在处理某些类型的非结构化、半结构化数据以及应对高并发、大规模分布式场景时,逐渐显露出局限性。正是在这样的背景下,NoSQL 数据库应运而生,而 MongoDB 作为其中的佼佼者,凭借其灵活的数据模型、强大的查询能力和易于扩展的特性,迅速成为了最受欢迎的 NoSQL 数据库之一。
如果你是一名开发者、数据分析师,或者只是对数据库技术感兴趣,想要了解并掌握 MongoDB,那么恭喜你,这篇教程将带你从零开始,一步步揭开 MongoDB 的神秘面纱,让你“一篇搞懂”其核心概念和基本操作。
本文将覆盖以下内容:
- 什么是 MongoDB?为何选择它?
- NoSQL 数据库概述
- MongoDB 的定义与特性
- MongoDB 的核心优势
- MongoDB 的典型应用场景
- MongoDB 的核心概念
- 数据库(Database)
- 集合(Collection)
- 文档(Document)
- BSON (Binary JSON)
- 与关系型数据库的对比
- 安装与配置(环境准备)
- 获取 MongoDB
- 安装步骤(概要)
- 启动 MongoDB 服务
- 连接 MongoDB Shell
- 使用 MongoDB Shell 进行基本操作
- 数据库操作:创建、切换、查看、删除
- 集合操作:创建、查看、删除
- 文档操作:CRUD(创建、读取、更新、删除)
- 插入文档 (Create)
- 查询文档 (Read)
- 更新文档 (Update)
- 删除文档 (Delete)
- 更高级的查询技巧
- 比较运算符、逻辑运算符
- 数组查询
- 内嵌文档查询
- 投影(Projection)
- 排序(Sort)、限制(Limit)、跳过(Skip)
- 索引 (Index)
- 为什么需要索引?
- 创建与管理索引
- 聚合框架 (Aggregation Framework) 简介
- 什么是聚合?
- 聚合管道的概念
- 基本聚合阶段示例
- MongoDB 的优缺点分析
- 总结与下一步学习建议
让我们开始这段 MongoDB 探索之旅吧!
1. 什么是 MongoDB?为何选择它?
NoSQL 数据库概述
在了解 MongoDB 之前,先简单回顾一下数据库的发展。传统数据库以关系型数据库(如 MySQL, PostgreSQL, Oracle, SQL Server)为主流,它们基于关系模型,数据存储在具有固定模式(schema)的表格中,使用 SQL (Structured Query Language) 进行操作。关系型数据库在处理结构化数据、保持数据一致性(ACID 事务)方面表现出色。
然而,面对 Web 2.0 时代以降的数据规模和复杂性挑战,关系型数据库的局限性日益凸显:
- 扩展性: 垂直扩展(升级硬件)成本高,水平扩展(分布式)复杂。
- 灵活性: 修改固定模式需要停机或复杂操作,难以适应快速迭代的应用需求。
- 性能: 面对高并发读写和非结构化数据查询时,性能可能下降。
为了应对这些挑战,出现了各种非关系型数据库,统称为 NoSQL (Not Only SQL)。NoSQL 数据库种类繁多,根据数据模型可分为:
- 键值存储 (Key-Value Store): Redis, Memcached
- 文档存储 (Document Store): MongoDB, Couchbase
- 列族存储 (Column-Family Store): Cassandra, HBase
- 图数据库 (Graph Database): Neo4j
NoSQL 数据库通常具有以下特点:
- 灵活的模式: 大多采用无模式(Schema-less)或弹性模式(Flexible Schema)。
- 易于扩展: 多数支持水平扩展,通过分布式集群来处理海量数据和高并发。
- 高性能: 针对特定数据模型和应用场景进行了优化。
- 最终一致性: 为了追求高性能和高可用,牺牲了部分 ACID 特性,倾向于实现 BASE (Basically Available, Soft state, Eventually consistent) 模型。
MongoDB 的定义与特性
MongoDB (来自 “humongous”,意为巨大) 是一个开源的、高性能、高可用、易扩展的文档数据库。它由 MongoDB Inc. 开发和维护。
其核心特性包括:
- 面向文档: 数据以 BSON 格式的文档形式存储,文档结构灵活,类似于 JSON。
- 模式自由: 同一个集合中的文档可以有不同的结构,这大大提高了开发的灵活性。
- 高可用性: 通过复制集(Replica Set)机制实现数据的冗余备份和自动故障转移。
- 易扩展性: 通过分片(Sharding)机制支持水平扩展,处理海量数据。
- 丰富的查询语言: 支持丰富的查询表达式、范围查询、正则表达式查询等。
- 高性能: 支持内嵌文档和数组,减少了 JOIN 操作,提高了读写性能。
- 自动分片: 简化了分布式数据库的部署和管理。
MongoDB 的核心优势
- 灵活的数据模型: 无需预定义模式,可以轻松存储和管理结构多样的数据,非常适合敏捷开发。
- 易于扩展: 内建的复制集和分片功能使得扩展能力强大,能够应对业务增长带来的数据量和并发量的挑战。
- 高性能: BSON 格式、内嵌文档以及优秀的索引支持,使得读写操作非常高效。
- 降低开发复杂度: 直接存储 JSON-like 的文档,与现代编程语言的数据结构天然契合,减少了 ORM 映射的麻烦。
- 丰富的特性: 支持地理空间索引、全文搜索、内存存储引擎等高级功能。
- 社区活跃且成熟: 拥有庞大的用户群体和完善的文档、驱动程序支持。
MongoDB 的典型应用场景
- 内容管理系统 (CMS): 文章、评论等数据结构不固定。
- 实时分析: 快速 ingest 大量时序数据或事件数据。
- 物联网 (IoT): 存储来自各种设备、格式多样的传感器数据。
- 移动应用: 作为移动应用的后端数据存储,同步和离线能力好。
- 用户画像/个性化: 存储具有不同属性的用户数据。
- 游戏应用: 存储玩家信息、游戏状态等。
- 电商平台: 产品目录、用户订单、评论等。
- 日志和分析平台: 存储和查询大量的日志数据。
2. MongoDB 的核心概念
理解 MongoDB 的核心概念是掌握它的基础。它与关系型数据库有一些相似之处,但也有关键的区别。
关系型数据库概念 | MongoDB 概念 | 说明 |
---|---|---|
数据库 (Database) | 数据库 (Database) | 物理上的文件系统上的目录,包含多个集合。 |
表 (Table) | 集合 (Collection) | 一组文档的容器,类似于表,但没有固定模式。 |
行 (Row) | 文档 (Document) | MongoDB 中数据的基本单元,一个 BSON 格式的键值对集合,类似于 JSON 对象。 |
列 (Column) | 字段 (Field) | 文档中的一个键值对,值可以是各种数据类型,包括内嵌文档或数组。 |
模式 (Schema) | 无模式 (Schema-less) 或 弹性模式 (Flexible Schema) | 集合中的文档可以有不同的结构和字段。 |
主键 (Primary Key) | _id 字段 |
每个文档必须有的唯一标识符,由 MongoDB 自动生成或用户自定义。 |
外键 (Foreign Key) | 引用 (Reference) 或 内嵌 (Embedding) | 通过在一个文档中引用另一个文档的 _id 或将相关文档直接嵌入到当前文档中来建立关系。 |
Join 操作 | 聚合 (Aggregation) 或 应用层处理 | MongoDB 通常推荐通过内嵌或引用来避免 JOIN,复杂关联通过聚合框架或应用层处理。 |
数据库 (Database)
数据库是物理上分离的集合容器。你可以创建多个数据库,每个数据库包含独立的集合。例如,你可能有一个用于用户管理的数据库,另一个用于产品目录的数据库。默认情况下,当你连接到 MongoDB 实例时,会有一个 test
数据库。
集合 (Collection)
集合是 MongoDB 中存储文档的地方。它类似于关系型数据库中的“表”,但不同之处在于,集合是无模式的(schema-less)。这意味着同一个集合中的文档可以具有不同的结构、字段和数据类型。集合存储在数据库中。
你可以显式创建集合,但更常见的是,当你向一个不存在的集合中插入第一个文档时,MongoDB 会隐式创建该集合。
文档 (Document)
文档是 MongoDB 中数据的基本单元。它是一个由键值对组成的有序集合。文档的结构与 JSON 对象非常相似,但 MongoDB 内部使用的是 BSON 格式。
文档示例:
json
{
"_id": ObjectId("60a7b1c2d3e4f5a6b7c8d9e0"), // 每个文档自动生成的唯一ID
"name": "张三",
"age": 30,
"isStudent": false,
"courses": ["数学", "英语", "计算机"], // 数组
"address": { // 内嵌文档
"city": "北京",
"zip": "100000"
},
"createdAt": ISODate("2023-10-27T10:00:00Z")
}
文档的特点:
- 键是字符串。
- 值可以是各种 BSON 数据类型,包括字符串、整数、浮点数、布尔值、数组、内嵌文档、日期、ObjectId 等。
- 键值对是有序的。
- 每个文档都有一个唯一的
_id
字段作为主键。如果没有提供_id
,MongoDB 会自动生成一个 ObjectId 作为_id
。
BSON (Binary JSON)
BSON 是一种二进制序列化格式,用于存储 MongoDB 文档。它是 JSON 的超集,额外支持了 JSON 中没有的数据类型,比如 Date、ObjectId、Binary data 等。
BSON 相比 JSON 的优势:
- 速度: BSON 解析速度比 JSON 快。
- 效率: 包含长度信息,使得跳过文档或数组中的元素更容易。
- 更多数据类型: 支持更多类型,如日期、二进制数据等。
虽然 MongoDB 内部使用 BSON 存储,但在与用户交互时(如使用 Shell 或驱动程序),通常会将其表示为 JSON 格式,方便阅读和理解。
与关系型数据库的对比总结
特性 | 关系型数据库 (e.g., MySQL) | MongoDB (文档数据库) |
---|---|---|
数据模型 | 关系模型 (表格, 行, 列) | 文档模型 (文档, 字段) |
模式 | 固定模式 | 无模式或弹性模式 |
数据单元 | 行 | 文档 |
数据存储 | 表 | 集合 |
关联关系 | Join 操作 | 内嵌文档 或 引用 (_id ) |
主键 | 用户定义或自增 ID | _id (通常是 ObjectId 自动生成) |
扩展性 | 垂直扩展为主,水平扩展复杂 | 水平扩展(分片 Sharding)容易 |
范式化 | 高度范式化 | 反范式化(内嵌)或 部分范式化(引用) |
事务 (ACID) | 强 ACID 事务支持 | MongoDB 4.0+ 支持跨文档事务,但与关系型数据库的事务模型有所不同;通常追求最终一致性。 |
查询语言 | SQL | MongoDB Query Language (基于 JSON-like) |
理解这些核心概念及其与关系型数据库的区别,有助于你更好地适应 MongoDB 的思维方式。
3. 安装与配置(环境准备)
在动手实践之前,你需要先安装 MongoDB 服务和客户端工具。
(注:以下步骤是概要性的,具体操作请参考 MongoDB 官方文档,因为安装步骤会随版本和操作系统变化而有所不同。)
-
获取 MongoDB:
- 访问 MongoDB 官方下载页面 (mongodb.com/try/download)。
- 选择适合你操作系统的版本(Windows, macOS, Linux)。推荐下载 Community Server 版本,它是免费的。
-
安装步骤:
- Windows: 下载 MSI 安装包,双击运行,按照向导进行安装。可以选择自定义安装路径和服务安装选项。
- macOS: 可以下载
.tgz
压缩包手动安装,或者使用 Homebrew (brew tap mongodb/brew && brew install mongodb-community
) 安装。 - Linux: 大多数 Linux 发行版都有包管理器(apt, yum, dnf),可以按照官方文档的指引添加 MongoDB 仓库,然后使用包管理器安装。
-
启动 MongoDB 服务 (
mongod
):- 安装完成后,需要启动 MongoDB 数据库服务器进程,通常是
mongod
命令。 - 默认情况下,
mongod
会尝试在/data/db
(Linux/macOS) 或C:\data\db
(Windows) 目录下创建数据目录。如果这些目录不存在或没有写入权限,你需要手动创建或指定--dbpath
参数。 - 启动命令示例:
mongod --dbpath /path/to/data/db
- 在 Windows 上,安装程序通常会将其作为服务安装并自动启动。在 Linux/macOS 上,你可能需要手动启动或配置 systemd/launchd 服务。
- 检查服务是否成功启动:查看控制台输出或日志文件。
- 安装完成后,需要启动 MongoDB 数据库服务器进程,通常是
-
连接 MongoDB Shell (
mongosh
):- MongoDB Shell 是一个交互式 JavaScript 界面,用于与 MongoDB 实例进行交互。较新的版本推荐使用
mongosh
,旧版本是mongo
。 - 安装 MongoDB Server 时通常会包含 Shell。
- 打开新的终端或命令提示符窗口。
- 输入
mongosh
命令并回车。如果mongod
服务在本地默认端口 (27017) 运行,Shell 会自动连接。 - 连接成功后,会显示 MongoDB 版本信息和提示符 (
>
或test>
)。
- MongoDB Shell 是一个交互式 JavaScript 界面,用于与 MongoDB 实例进行交互。较新的版本推荐使用
“`bash
示例:启动 mongod (如果安装时没有配置为服务)
mongod
示例:打开新的终端,连接 mongosh
mongosh
“`
看到类似以下输出表示连接成功:
Current Mongosh version: X.X.X
...
Connecting to: mongodb://127.0.0.1:27017/?directConnection=true&serverSelectionTimeoutMS=2000
test>
提示符前的 test
表示当前所在的数据库是 test
。
4. 使用 MongoDB Shell 进行基本操作 (CRUD)
一旦连接到 mongosh
,你就可以开始执行数据库操作了。MongoDB 的 Shell 使用 JavaScript 语法。
数据库操作
- 查看所有数据库:
javascript
show dbs
这会列出所有存在的数据库。默认有admin
,config
,local
这几个系统数据库。 - 切换或创建数据库:
javascript
use mydatabase
如果mydatabase
存在,则切换到该数据库;如果不存在,则创建一个新的数据库(直到你向其中插入第一个文档)。 - 查看当前所在数据库:
javascript
db - 删除当前数据库:
javascript
db.dropDatabase()
注意: 这个操作会永久删除当前数据库及其所有数据,请谨慎使用!
集合操作
切换到你的数据库后(例如,使用 use mydatabase
),你可以操作集合。
- 查看当前数据库中的所有集合:
javascript
show collections - 创建集合:
显式创建集合不常用,因为插入文档时会自动创建。但如果需要预设验证规则或指定特定选项,可以使用createCollection()
:
javascript
db.createCollection("users") - 删除集合:
javascript
db.collectionName.drop()
例如,删除users
集合:
javascript
db.users.drop()
注意: 这也会永久删除该集合及其所有文档,请谨慎使用!
文档操作 (CRUD)
这是最核心的部分:创建(Create)、读取(Read)、更新(Update)、删除(Delete) 文档。所有这些操作都在特定的集合上执行。
假设我们切换到了 mydatabase
数据库,并将在一个名为 students
的集合中进行操作。
C – 创建文档 (Insert)
- 插入一个文档: 使用
insertOne()
方法。
javascript
db.students.insertOne({
name: "李华",
age: 22,
major: "Computer Science",
grades: ["A", "B", "A"],
contact: {
email: "[email protected]",
phone: "1234567890"
},
enrollmentDate: new Date() // 可以使用 Date 对象
})
如果成功,它会返回一个包含insertedId
的结果对象。 - 插入多个文档: 使用
insertMany()
方法,传入一个文档数组。
javascript
db.students.insertMany([
{
name: "王芳",
age: 23,
major: "Software Engineering",
grades: ["B", "A", "C"],
contact: { email: "[email protected]" }
},
{
name: "赵明",
age: 21,
major: "Mathematics",
grades: ["A", "A", "B"],
isGraduated: false // 不同文档可以有不同字段
}
])
如果成功,它会返回一个包含insertedIds
数组的结果对象。
R – 读取文档 (Read)
读取操作主要使用 find()
方法。
- 查找所有文档:
javascript
db.students.find()
这会返回students
集合中的所有文档。在 Shell 中默认只显示前 20 个结果,输入it
可以查看更多。 - 查找符合条件的文档:
find()
方法接受一个查询文档作为参数,指定匹配条件。
查找年龄大于等于 22 岁的学生:
javascript
db.students.find({ age: { $gte: 22 } }) // $gte 是比较运算符
查找专业是 “Computer Science” 的学生:
javascript
db.students.find({ major: "Computer Science" })
查找名字是 “王芳” 且年龄小于 25 岁的学生:
javascript
db.students.find({ name: "王芳", age: { $lt: 25 } }) // 多个条件默认是 AND 关系
查找grades
数组中包含 “A” 的学生:
javascript
db.students.find({ grades: "A" }) // 直接匹配数组元素
查找联系方式中有 email 字段的学生:
javascript
db.students.find({ "contact.email": { $exists: true } }) // 查询内嵌文档字段用点语法 - 查找单个文档:
findOne()
方法返回符合条件的第一个文档。
javascript
db.students.findOne({ name: "李华" })
如果没有找到,返回null
。
U – 更新文档 (Update)
更新操作主要使用 updateOne()
和 updateMany()
方法。它们都需要至少两个参数:查询文档(指定要更新哪些文档)和更新操作符(指定如何修改文档)。
- 使用
$set
设置字段值: 修改匹配文档中指定字段的值。
更新名字为 “李华” 的学生的年龄为 23 岁:
javascript
db.students.updateOne(
{ name: "李华" }, // 查询条件
{ $set: { age: 23 } } // 更新操作:设置 age 字段为 23
)
更新所有学生的isEnrolled
字段为true
(如果不存在则添加):
javascript
db.students.updateMany(
{}, // 空查询文档匹配所有文档
{ $set: { isEnrolled: true } }
) - 使用
$inc
增加数值: 对数值字段进行增减。
将所有学生的年龄增加 1 岁:
javascript
db.students.updateMany(
{},
{ $inc: { age: 1 } } // 增加 age 字段的值 1
) - 使用
$push
向数组添加元素:
向名字为 “李华” 的学生的grades
数组中添加一个元素 “B”:
javascript
db.students.updateOne(
{ name: "李华" },
{ $push: { grades: "B" } }
) - 使用
$unset
删除字段:
删除所有学生的contact.phone
字段:
javascript
db.students.updateMany(
{},
{ $unset: { "contact.phone": "" } } // $unset 的值可以是任意类型,通常用 "" 或 1
) updateOne
/updateMany
的第三个参数:Options
可以包含upsert: true
选项。如果查询条件没有匹配到文档,upsert: true
会创建一个新文档。
javascript
db.students.updateOne(
{ name: "陈明" },
{ $set: { age: 20, major: "History" } },
{ upsert: true } // 如果陈明不存在,则创建一个新的文档
)
D – 删除文档 (Delete)
删除操作主要使用 deleteOne()
和 deleteMany()
方法。它们都需要一个查询文档作为参数,指定要删除哪些文档。
- 删除一个文档: 删除符合条件的第一个文档。
删除名字为 “李华” 的学生:
javascript
db.students.deleteOne({ name: "李华" }) - 删除多个文档: 删除所有符合条件的文档。
删除年龄大于 25 岁的学生:
javascript
db.students.deleteMany({ age: { $gt: 25 } }) - 删除集合中的所有文档: 传入一个空查询文档
{}
。
javascript
db.students.deleteMany({}) // 这会清空 students 集合,但集合本身依然存在
注意:清空集合通常更推荐使用db.students.drop()
,它效率更高,直接删除并重建集合。
5. 更高级的查询技巧
MongoDB 的查询语言非常灵活和强大。
比较运算符
运算符 | 描述 | 示例 |
---|---|---|
$eq |
等于 (默认) | { age: 22 } |
$ne |
不等于 | { age: { $ne: 22 } } |
$gt |
大于 | { age: { $gt: 22 } } |
$lt |
小于 | { age: { $lt: 22 } } |
$gte |
大于等于 | { age: { $gte: 22 } } |
$lte |
小于等于 | { age: { $lte: 22 } } |
$in |
在数组中 | { major: { $in: ["CS", "SE"] } } |
$nin |
不在数组中 | { major: { $nin: ["CS", "SE"] } } |
逻辑运算符
运算符 | 描述 | 示例 |
---|---|---|
$and |
逻辑与 | { $and: [{ age: { $gt: 20 } }, { age: { $lt: 30 } }] } |
$or |
逻辑或 | { $or: [{ major: "CS" }, { major: "EE" }] } |
$not |
逻辑非 | { age: { $not: { $gt: 25 } } } (年龄不大于25) |
$nor |
逻辑非或 (都不) | { $nor: [{ age: 22 }, { major: "CS" }] } (年龄不等于22 且 专业不是CS) |
注意:多个字段的查询条件默认就是 $and
关系,例如 { name: "李华", age: 22 }
等同于 { $and: [{ name: "李华" }, { age: 22 }] }
。 $and
运算符主要用于同一个字段的多个条件,或者需要显式组合条件的情况。
数组查询
- 查询数组包含特定元素:
javascript
db.students.find({ grades: "A" }) // 查找 grades 数组中包含 "A" 的文档 - 查询数组包含所有指定元素: 使用
$all
。
javascript
db.students.find({ grades: { $all: ["A", "B"] } }) // 查找 grades 数组中同时包含 "A" 和 "B" 的文档 - 查询数组按索引位置的元素:
javascript
db.students.find({ "grades.0": "A" }) // 查找 grades 数组第一个元素是 "A" 的文档 - 查询数组长度: 使用
$size
。
javascript
db.students.find({ grades: { $size: 3 } }) // 查找 grades 数组长度是 3 的文档
内嵌文档查询
使用点语法 (.
) 来访问内嵌文档的字段。
- 查询内嵌文档的精确匹配:
javascript
db.students.find({ contact: { email: "[email protected]", phone: "1234567890" } }) // 要求 contact 文档结构完全一致 - 查询内嵌文档的特定字段:
javascript
db.students.find({ "contact.email": "[email protected]" }) // 查找 contact 内嵌文档中 email 字段是特定值的文档 - 查询内嵌文档的字段使用运算符:
javascript
db.students.find({ "address.zip": { $gt: "100000" } })
投影 (Projection)
find()
方法的第二个参数是一个投影文档,用于指定返回的文档中包含哪些字段。这有助于减少网络传输的数据量。
- 只返回特定字段: 在投影文档中,将要返回的字段值设置为
1
。默认会返回_id
字段,如果要排除_id
,将其设置为0
。
javascript
db.students.find(
{ major: "Computer Science" }, // 查询条件
{ name: 1, major: 1, _id: 0 } // 投影:只返回 name 和 major 字段,不返回 _id
) - 排除特定字段: 将不希望返回的字段值设置为
0
。
javascript
db.students.find(
{ age: { $lt: 25 } }, // 查询条件
{ grades: 0, contact: 0 } // 投影:排除 grades 和 contact 字段,其他字段默认返回 (包括 _id)
)
注意:不能在同一个投影文档中同时包含包含 (1) 和排除 (0) 字段,除了_id
字段。
排序 (Sort)、限制 (Limit)、跳过 (Skip)
这些方法可以链式调用在 find()
后面。
- 排序: 使用
sort()
方法,接受一个排序文档{ field: direction }
。direction
为1
表示升序,-1
表示降序。
按年龄升序排序:
javascript
db.students.find().sort({ age: 1 })
先按专业升序,再按年龄降序排序:
javascript
db.students.find().sort({ major: 1, age: -1 }) - 限制结果数量: 使用
limit()
方法,接受一个整数参数。
只返回前 5 个学生:
javascript
db.students.find().limit(5) - 跳过结果数量: 使用
skip()
方法,接受一个整数参数。常用于分页。
跳过前 10 个学生,返回之后的结果:
javascript
db.students.find().skip(10) - 组合使用 (分页示例): 返回第 2 页的学生,每页 10 条(跳过前 10 条,再取 10 条)。
javascript
db.students.find().skip(10).limit(10)
注意:skip()
在大型数据集上的性能可能不高,尤其是在没有有效索引的情况下。
6. 索引 (Index)
索引在数据库中扮演着至关重要的角色,它可以显著提高查询效率。MongoDB 的索引概念与关系型数据库类似。
-
为什么需要索引?
默认情况下,MongoDB 对集合中的所有文档进行全集合扫描 (collection scan) 来查找匹配的文档。当集合很小的时候这没问题,但对于大型集合,全集合扫描会非常慢。
索引就像书的目录,它存储了特定字段的值以及这些值对应文档的物理位置指针。通过索引,数据库可以直接定位到包含目标值的文档,而无需扫描整个集合。 -
创建与管理索引:
使用createIndex()
方法为集合创建索引。
为name
字段创建升序索引:
javascript
db.students.createIndex({ name: 1 })
为age
字段创建降序索引:
javascript
db.students.createIndex({ age: -1 })
创建复合索引(在多个字段上创建索引,查询时如果条件包含这些字段,可以同时利用索引):
javascript
db.students.createIndex({ major: 1, age: -1 }) // 先按 major 升序,再按 age 降序
查看集合的所有索引:
javascript
db.students.getIndexes()
删除索引:
javascript
db.students.dropIndex("index_name") // index_name 可以通过 getIndexes() 查看
或者删除所有索引:
javascript
db.students.dropIndexes()
创建索引是一个耗时且占用资源的操作,特别是对于大型集合,最好在非高峰时段执行。索引会占用额外的存储空间,并且在写操作(插入、更新、删除)时需要额外维护,因此并非越多越好。选择合适的字段创建索引是优化 MongoDB 性能的关键。
7. 聚合框架 (Aggregation Framework) 简介
聚合(Aggregation)是 MongoDB 中一个强大的数据处理工具,它允许你对文档集合进行一系列的数据转换操作(管道),从而计算出聚合结果。这类似于 SQL 中的 GROUP BY、JOIN(简化)、数据转换等操作。
-
什么是聚合?
聚合用于对文档进行处理,例如:- 计算集合中文档的数量。
- 按某个字段分组并计算每组的总和、平均值等。
- 重构文档结构、添加新字段、删除字段。
- 对文档进行过滤、排序、限制。
- 联接来自不同集合的数据(使用
$lookup
)。
-
聚合管道的概念:
MongoDB 的聚合框架是基于管道 (Pipeline) 的概念设计的。数据文档进入一个管道,经过一系列阶段 (Stage) 的处理,每个阶段对输入的文档进行操作并输出结果文档,这些结果文档再作为下一个阶段的输入,直到管道结束,得到最终的聚合结果。 -
基本聚合阶段示例:
聚合操作使用aggregate()
方法,接受一个包含多个阶段的数组作为参数。示例:按专业分组,计算每个专业的学生数量
javascript
db.students.aggregate([
{
// Stage 1: $group - 按 major 字段分组,计算每组的计数
$group: {
_id: "$major", // 按 major 字段的值分组,$major 引用输入文档的 major 字段
count: { $sum: 1 } // 在每组中,对每个文档加 1,求和得到计数
}
},
{
// Stage 2: $sort - 按计数降序排序
$sort: { count: -1 }
},
{
// Stage 3: $project - 重塑输出文档结构,只包含 major 和 studentCount 字段
$project: {
_id: 0, // 不显示 _id
major: "$_id", // 将 _id (即 major 值) 重命名为 major
studentCount: "$count" // 将 count 重命名为 studentCount
}
}
])
这个例子展示了聚合管道中几个常见的阶段:
*$group
: 对文档进行分组。_id
指定分组的键。
*$sort
: 对分组后的结果进行排序。
*$project
: 对输出文档进行重塑,选择、排除或重命名字段。还有很多其他阶段,如
$match
(过滤文档)、$limit
(限制结果数量)、$skip
(跳过结果数量)、$unwind
(展开数组)、$lookup
(联接) 等。聚合框架非常强大,可以完成复杂的分析任务。对于入门阶段,理解管道的概念以及$match
和$group
的基本用法是很好的开始。
8. MongoDB 的优缺点分析
优点:
- 灵活性: 无模式设计使得数据模型可以快速适应需求变化。
- 扩展性: 内建的复制集和分片支持易于实现高可用和水平扩展。
- 高性能: 文档模型、BSON、强大的索引和内嵌文档减少了 JOIN 开销。
- 易于使用: JSON-like 的文档结构与现代开发语言契合度高,学习曲线相对平缓。
- 功能丰富: 支持地理空间索引、全文搜索、事务等。
缺点:
- 复杂关联查询: 对于需要复杂多表 JOIN 的场景,不如关系型数据库直观和高效(虽然聚合框架提供了
$lookup
,但在复杂性上可能不及 SQL)。 - 强一致性: 为了追求高性能和可用性,默认设置下可能不是强一致性的,需要理解其一致性模型(可读性偏好)。
- 存储开销: 内嵌文档可能导致数据冗余,增加存储空间。字段名存储在每个文档中也会有额外开销。
- 模式管理: 虽然无模式是优点,但也意味着缺乏强制性的数据约束,应用层需要负责数据格式的校验。
- 事务支持: 4.0 版本后支持了多文档事务,但使用场景、性能、与传统 ACID 的语义细节需要深入理解。
总体而言,MongoDB 非常适合需要高伸缩性、处理灵活数据结构、以及面向现代互联网应用的场景。对于高度结构化且需要复杂事务和多表关联的传统业务,关系型数据库可能仍是更好的选择。
9. 总结与下一步学习建议
通过本文,你应该已经对 MongoDB 有了一个全面的入门了解,包括它的基本概念、核心优势、与关系型数据库的区别、以及使用 Shell 进行基本的 CRUD 操作和一些高级查询技巧。
我们学习了:
- MongoDB 是一个面向文档的 NoSQL 数据库。
- 它的核心是数据库、集合和文档。
- 数据存储为 BSON 格式的灵活文档。
- 可以使用
mongosh
进行交互式操作。 - 掌握了
insertOne
,insertMany
,find
,updateOne
,updateMany
,deleteOne
,deleteMany
等基本 CRUD 方法。 - 了解了常用的查询操作符和投影、排序、限制、跳过等。
- 理解了索引的重要性及其创建方式。
- 初步接触了强大的聚合框架。
下一步学习建议:
- 动手实践: 搭建自己的 MongoDB 环境,反复练习本文中的 Shell 命令,尝试不同的查询条件和数据结构。
- 深入查询语言: 探索更多查询操作符(如
$regex
,$where
,$expr
)、数组操作符($addToSet
,$pop
,$pull
)等。 - 学习聚合框架: 深入理解聚合管道的各个阶段,尝试构建更复杂的聚合查询来分析数据。
- 索引进阶: 学习不同类型的索引(文本索引、地理空间索引、哈希索引)、索引策略和使用
explain()
来分析查询性能。 - 复制集 (Replica Set): 了解如何搭建和配置复制集,实现数据高可用和故障转移。
- 分片 (Sharding): 学习如何搭建和配置分片集群,实现数据水平扩展,处理海量数据。
- 驱动程序 (Drivers): 在你常用的编程语言(Node.js, Python, Java, PHP 等)中使用官方或社区提供的 MongoDB 驱动程序,将 MongoDB 集成到你的应用中。
- MongoDB Atlas: 了解 MongoDB 提供的云数据库服务,体验更便捷的部署和管理。
- 安全: 学习如何配置用户认证和访问控制。
- 监控与维护: 了解如何监控 MongoDB 实例的性能和健康状况,以及备份恢复策略。
MongoDB 是一个功能强大且不断发展的数据库系统。入门只是第一步,持续学习和实践是掌握它的关键。
希望这篇长文能够帮助你“一篇搞懂”MongoDB 的入门知识,并为你后续的学习打下坚实的基础!祝你学习顺利!