MongoDB 入门:从了解到开始 – wiki基地


MongoDB 入门:从了解到开始,迈出 NoSQL 数据库的第一步

在现代应用开发的世界里,数据是核心。选择合适的数据库系统对于应用的性能、可伸缩性、可用性以及开发效率至关重要。长期以来,关系型数据库(RDBMS),如 MySQL、PostgreSQL、SQL Server,以其结构化的表格、严格的模式(Schema)和强大的事务处理能力占据主导地位。然而,随着互联网应用的爆炸式增长,数据量呈几何级数上升,数据结构也日益复杂且多变,传统的 RDBMS 在面对高并发、大数据量以及快速迭代需求时,逐渐显露出其不足之处,尤其是在水平扩展方面。

正是在这样的背景下,NoSQL(Not Only SQL)数据库应运而生,为开发者提供了更多样化的数据存储和访问模型。NoSQL 数据库没有固定的表格模式,通常更容易实现水平扩展,并能更好地适应非结构化或半结构化数据。在众多 NoSQL 数据库中,MongoDB 凭借其灵活的文档模型、强大的功能和易用性,迅速崛起并成为最受欢迎的 NoSQL 数据库之一。

本文将带您踏上 MongoDB 的入门之旅,从认识它是什么、为何选择它,到如何安装、连接并进行最基础的数据操作(CRUD),帮助您建立起对 MongoDB 的初步认知和实践能力。

第一部分:了解到 MongoDB – 认识与理解

1. MongoDB 是什么?

MongoDB 是一个开源的、高性能、高可用、可伸缩的 文档型数据库。它属于 NoSQL 数据库家族中的一员。与传统的关系型数据库不同,MongoDB 不使用表格、行和列的概念,而是使用文档(Document)集合(Collection)数据库(Database)

  • 文档(Document): MongoDB 的核心单元。一个文档类似于 JSON(JavaScript Object Notation)对象,以 BSON(Binary JSON)格式存储。文档可以包含键值对,值可以是基本数据类型(字符串、数字、布尔值、日期等),也可以是数组或嵌套的文档。这种结构使得文档能够非常灵活地表示复杂的层级关系数据。
  • 集合(Collection): 一组文档的集合。集合类似于关系型数据库中的表(Table)。一个集合中的文档可以有不同的结构(字段),但通常它们代表同一类事物(例如,一个 users 集合包含多个用户文档)。
  • 数据库(Database): 集合的容器。一个 MongoDB 实例可以包含多个数据库,每个数据库拥有独立的集合。

2. 为什么选择 MongoDB?NoSQL vs. SQL

了解 MongoDB 的优势,需要将其与传统 SQL 数据库进行对比,并理解 NoSQL 诞生的原因:

  • 数据模型的灵活性 (Schema-less / Flexible Schema):

    • SQL: 需要预先定义严格的表结构(Schema)。如果需要修改结构,通常需要执行耗时的 ALTER TABLE 操作,这在快速迭代的开发环境中不够灵活。
    • NoSQL (MongoDB): 文档具有灵活的结构。同一个集合中的文档可以有不同的字段。这极大地简化了数据结构的变更,使得开发者可以更快地响应需求变化。例如,为用户添加一个新的可选字段,只需在需要该字段的文档中添加即可,无需修改整个集合的模式。
    • 优势: 更快的开发周期,更适合存储结构多变或难以预先确定结构的数据。
  • 易于横向扩展 (Scalability):

    • SQL: 传统的扩展方式是纵向扩展(Vertical Scaling),即提升服务器的硬件性能(CPU、内存、磁盘)。这种方式成本高昂且有上限。横向扩展(Horizontal Scaling),即增加更多的服务器来分担负载,在 SQL 数据库中实现复杂且困难,通常需要复杂的复制和分片(Sharding)策略。
    • NoSQL (MongoDB): MongoDB 设计之初就考虑了横向扩展。通过内置的 分片(Sharding) 机制,可以将大量数据分散存储在多台服务器上,极大地提升了处理能力和存储容量。
    • 优势: 能够轻松应对海量数据和高并发访问的需求,降低单台服务器的压力,节约成本。
  • 高性能 (Performance):

    • MongoDB 内部使用 BSON 格式,解析速度快。
    • 支持丰富的索引类型(单键索引、复合索引、地理空间索引、全文索引等),能极大地加速查询。
    • 数据倾向于以嵌入文档的方式存储,减少了复杂的关联查询(Join),提高了读写效率。
    • 数据通常存储在磁盘上,但会大量使用内存作为缓存(Working Set),频繁访问的数据可以直接从内存中获取,速度更快。
    • 支持副本集(Replica Set),提供读写分离的能力,可以将读请求分散到 Secondary 节点,提高读吞吐量。
  • 高可用性 (High Availability):

    • 通过 副本集(Replica Set) 机制实现。一个副本集是一组维护相同数据副本的 MongoDB 进程。其中一个节点是主节点(Primary),负责处理所有写操作;其他节点是从节点(Secondaries),异步复制主节点的数据。
    • 当主节点发生故障时,副本集会自动选举一个新的主节点,保证服务的持续可用,减少停机时间。
    • 优势: 保证了数据库服务的健壮性和可靠性,适用于需要高可用性的生产环境。
  • 丰富的查询语言: 虽然是 NoSQL,但 MongoDB 提供了强大的查询语言,支持字段查询、范围查询、正则匹配等,以及用于复杂数据处理的 聚合框架(Aggregation Framework),其能力不亚于 SQL 中的 GROUP BY、JOIN(尽管实现方式不同)等操作。

3. MongoDB 的典型应用场景

鉴于其上述特性,MongoDB 特别适合以下应用场景:

  • Web 应用: 用户档案、会话管理、内容管理等,应对高流量和数据结构灵活的需求。
  • 移动应用: 常常需要处理非结构化或半结构化的用户数据和应用数据。
  • 内容管理系统 (CMS): 文章、博客、用户评论等,内容类型多样,结构不固定。
  • 实时分析 (Real-time Analytics): 快速 ingest 大量流式数据,进行实时处理和查询。
  • 物联网 (IoT): 收集来自大量设备、结构各异的传感器数据。
  • 游戏: 存储玩家数据、游戏状态等。
  • 目录服务: 产品目录、元数据管理等。

4. MongoDB 的一些需要注意的地方

  • 事务支持: 早期版本的 MongoDB 仅支持单文档事务。从 4.0 版本开始,MongoDB 提供了多文档事务功能,但相比传统关系型数据库的 ACID 事务,其应用场景和性能特性有所不同,需要开发者理解其隔离级别和使用方式。
  • Schema 设计: 尽管是灵活模式,但良好的文档结构设计(例如,选择嵌入还是引用)对性能至关重要。过度嵌套或过度关联都可能带来问题。
  • 关联查询: MongoDB 推崇内嵌文档来减少关联。但当确实需要关联不同集合的数据时,可以通过 $lookup 操作符在聚合框架中实现类似 JOIN 的功能,但这可能不如 SQL 中的 JOIN 高效和直观。

第二部分:到开始 – 安装、连接与基础操作

了解了 MongoDB 是什么及其特性之后,最重要的一步就是亲手实践。本部分将指导您完成 MongoDB 的安装、连接,并进行最基础的数据增删改查 (CRUD) 操作。

1. 安装 MongoDB

MongoDB 提供了多种安装方式,包括:

  • 直接下载安装包: 从 MongoDB 官网下载适用于您的操作系统的 Community Server 版本安装包(Windows, macOS, Linux)。这是最常见的本地安装方式。
  • 使用包管理器: 在 Linux 上,可以使用 apt、yum 或 brew (macOS) 等包管理器进行安装。
  • 使用 Docker: 如果您熟悉容器化技术,可以使用官方提供的 Docker 镜像。
  • 云服务: 使用 MongoDB Atlas (MongoDB 官方提供的云数据库服务)、AWS DocumentDB、Azure Cosmos DB (兼容 MongoDB API) 等云服务。

以 Windows 为例(简述步骤,详细请参考官方文档):

  1. 访问 MongoDB 官方下载页面:https://www.mongodb.com/try/download/community
  2. 选择您的操作系统和版本,下载 MSI 安装包。
  3. 运行安装包,选择自定义安装(Custom)。
  4. 选择安装路径。
  5. 选择安装服务(Install MongoDB as a Service)。可以选择以指定用户运行,或使用默认的网络服务用户。记住数据目录(Data Directory)和日志目录(Log Directory)的位置。
  6. 取消勾选安装 MongoDB Compass(或者选择安装,这是一个不错的 GUI 工具)。
  7. 完成安装。
  8. 安装完成后,需要手动创建数据目录和日志目录(如果在安装时没有自动创建或指定)。例如 C:\data\dbC:\data\log
  9. 确保 MongoDB 的 bin 目录(例如 C:\Program Files\MongoDB\Server\6.0\bin)被添加到系统的环境变量 PATH 中,这样才能在任意命令行窗口运行 mongodmongosh 命令。

启动 MongoDB 服务:

安装为服务后,MongoDB 通常会随系统启动而自动运行。您也可以通过服务管理器手动启动或停止。

如果选择手动启动,打开命令行窗口,输入:
bash
mongod --dbpath <your_data_directory_path> --logpath <your_log_directory_path>\mongod.log --fork

其中 <your_data_directory_path> 是您指定的数据存储目录,<your_log_directory_path> 是日志目录。--fork 参数让服务在后台运行。

2. 连接到 MongoDB

安装并启动服务后,就可以连接到 MongoDB 数据库了。最常用的方式是使用 MongoDB Shell。

打开一个新的命令行窗口,输入:

bash
mongosh

如果一切正常,您将看到类似以下的输出,表示已成功连接到本地 MongoDB 实例(默认端口 27017):

“`
Current Mongosh active development version: 2.0.2

Using MongoDB: 6.0.5
Using Node.js: 16.20.0

test>
“`

test> 是 Shell 的提示符,表示当前连接到名为 test 的数据库(默认数据库)。

3. MongoDB Shell 基础操作

连接成功后,就可以在 Shell 中执行命令了。

3.1 选择数据库

使用 use <database_name> 命令来切换或创建一个新的数据库。如果指定的数据库不存在,第一次向其中插入数据时会自动创建。

“`javascript

use myapp_db
switched to db myapp_db
myapp_db>
``
现在,您正在操作
myapp_db` 数据库。

3.2 查看数据库和集合

  • 查看所有数据库:
    javascript
    myapp_db> show dbs
    admin 40.00 KiB
    config 112.00 KiB
    local 40.00 KiB
    myapp_db 8.00 KiB // 刚创建,数据量很小
  • 查看当前数据库中的所有集合:
    javascript
    myapp_db> show collections
    // 此时可能没有任何输出,因为还没有创建集合

3.3 创建 (Create) – 插入文档

使用 db.<collection_name>.insertOne()db.<collection_name>.insertMany() 方法向集合中插入文档。如果集合不存在,插入时会自动创建。

插入单个文档 (insertOne):

javascript
myapp_db> db.users.insertOne({
name: "张三",
age: 30,
city: "北京",
isStudent: false,
interests: ["编程", "旅行"]
})
{
acknowledged: true,
insertedId: ObjectId("...") // 会自动生成一个唯一的 _id
}

注意:_id 是 MongoDB 自动为每个文档生成的唯一标识符,类型是 ObjectId。如果您在插入时没有指定 _id 字段,MongoDB 会自动添加一个。

插入多个文档 (insertMany): 接受一个文档数组作为参数。

javascript
myapp_db> db.users.insertMany([
{ name: "李四", age: 25, city: "上海", interests: ["阅读", "音乐"] },
{ name: "王五", age: 22, city: "广州", major: "计算机科学", isStudent: true }
])
{
acknowledged: true,
insertedIds: {
'0': ObjectId("..."),
'1': ObjectId("...")
}
}

可以看到,王五 的文档结构与前面不同,它包含了 major 字段,而没有 interests。这就是文档模型的灵活性。

现在再次 show collections,您会看到 users 集合已经存在了。

javascript
myapp_db> show collections
users

3.4 读取 (Read) – 查询文档

使用 db.<collection_name>.find() 方法查询集合中的文档。

  • 查询所有文档: 不带参数的 find() 方法返回集合中的所有文档。
    javascript
    myapp_db> db.users.find()
    [
    {
    _id: ObjectId("655e5..."),
    name: '张三',
    age: 30,
    city: '北京',
    isStudent: false,
    interests: [ '编程', '旅行' ]
    },
    {
    _id: ObjectId("655e5..."),
    name: '李四',
    age: 25,
    city: '上海',
    interests: [ '阅读', '音乐' ]
    },
    {
    _id: ObjectId("655e5..."),
    name: '王五',
    age: 22,
    city: '广州',
    major: '计算机科学',
    isStudent: true
    }
    ]

    find() 返回的是一个游标(Cursor),在 Shell 中会自动遍历并打印前 20 个结果。

  • 按条件查询: find() 方法可以接受一个查询文档(Query Document)作为第一个参数,用来指定过滤条件。查询文档使用 { field: value } 的形式表示等于某个值。
    “`javascript
    // 查询 name 为 “张三” 的用户
    myapp_db> db.users.find({ name: “张三” })
    [
    {
    _id: ObjectId(“655e5…”),
    name: ‘张三’,
    age: 30,
    city: ‘北京’,
    isStudent: false,
    interests: [ ‘编程’, ‘旅行’ ]
    }
    ]

    // 查询 age 为 22 且 city 为 “广州” 的用户 (AND 条件,直接写多个键值对)
    myapp_db> db.users.find({ age: 22, city: “广州” })
    [
    {
    _id: ObjectId(“655e5…”),
    name: ‘王五’,
    age: 22,
    city: ‘广州’,
    major: ‘计算机科学’,
    isStudent: true
    }
    ]
    “`

  • 使用查询操作符: MongoDB 提供了丰富的查询操作符,如 $gt (大于), $lt (小于), $gte (大于等于), $lte (小于等于), $ne (不等于), $in (在数组中), $nin (不在数组中) 等。操作符通常用 { field: { <operator>: <value> } } 的形式。
    “`javascript
    // 查询 age 大于等于 25 的用户
    myapp_db> db.users.find({ age: { $gte: 25 } })
    [
    {
    _id: ObjectId(“655e5…”),
    name: ‘张三’,
    age: 30,
    city: ‘北京’,
    isStudent: false,
    interests: [ ‘编程’, ‘旅行’ ]
    },
    {
    _id: ObjectId(“655e5…”),
    name: ‘李四’,
    age: 25,
    city: ‘上海’,
    interests: [ ‘阅读’, ‘音乐’ ]
    }
    ]

    // 查询 interests 数组中包含 “编程” 的用户
    myapp_db> db.users.find({ interests: “编程” })
    [
    {
    _id: ObjectId(“655e5…”),
    name: ‘张三’,
    age: 30,
    city: ‘北京’,
    isStudent: false,
    interests: [ ‘编程’, ‘旅行’ ]
    }
    ]
    “`

  • OR 条件: 使用 $or 操作符,其值是一个条件文档的数组。
    javascript
    // 查询 city 为 "北京" 或 "上海" 的用户
    myapp_db> db.users.find({ $or: [{ city: "北京" }, { city: "上海" }] })
    [
    {
    _id: ObjectId("655e5..."),
    name: '张三',
    age: 30,
    city: '北京',
    isStudent: false,
    interests: [ '编程', '旅行' ]
    },
    {
    _id: ObjectId("655e5..."),
    name: '李四',
    age: 25,
    city: '上海',
    interests: [ '阅读', '音乐' ]
    }
    ]

  • 指定返回字段 (Projection): find() 方法可以接受第二个参数,一个投影文档(Projection Document),用来指定返回结果中包含或排除哪些字段。使用 { field: 1 } 表示包含该字段,{ field: 0 } 表示排除该字段。_id 字段默认返回,除非明确排除 { _id: 0 }
    javascript
    // 查询所有用户,只返回 name 和 city 字段 (并排除 _id)
    myapp_db> db.users.find({}, { name: 1, city: 1, _id: 0 })
    [
    { name: '张三', city: '北京' },
    { name: '李四', city: '上海' },
    { name: '王五', city: '广州' }
    ]

  • 查找单个文档: db.<collection_name>.findOne() 方法返回满足条件的第一个文档。
    javascript
    myapp_db> db.users.findOne({ name: "张三" })
    {
    _id: ObjectId("655e5..."),
    name: '张三',
    age: 30,
    city: '北京',
    isStudent: false,
    interests: [ '编程', '旅行' ]
    }

3.5 更新 (Update) – 修改文档

使用 db.<collection_name>.updateOne()db.<collection_name>.updateMany() 方法更新文档。

  • updateOne(): 更新满足条件的 第一个 文档。
  • updateMany(): 更新满足条件的 所有 文档。

这些方法都接受至少两个参数:第一个是查询文档(指定要更新哪个文档),第二个是更新文档(指定如何修改文档)。更新文档通常使用更新操作符,如 $set (设置字段值), $inc (增加数字字段的值), $unset (删除字段), $push (向数组字段添加元素) 等。

更新单个文档 (updateOne):

javascript
// 更新 name 为 "张三" 的文档,设置 age 为 31,并添加一个新字段 email
myapp_db> db.users.updateOne(
{ name: "张三" },
{ $set: { age: 31, email: "[email protected]" } }
)
{
acknowledged: true,
insertedId: null, // 不是插入操作
matchedCount: 1, // 找到一个匹配文档
modifiedCount: 1, // 修改了一个文档
upsertedId: null // 没有执行 upsert
}

再次查询 张三 的文档,会看到 age 变为 31,并多了 email 字段。

更新多个文档 (updateMany):

javascript
// 将所有 age 大于等于 25 的用户的 isStudent 字段设置为 false
myapp_db> db.users.updateMany(
{ age: { $gte: 25 } },
{ $set: { isStudent: false } }
)
{
acknowledged: true,
insertedId: null,
matchedCount: 2, // 找到了 张三 和 李四
modifiedCount: 2, // 修改了 2 个文档
upsertedId: null
}

即使 张三 原来的 isStudent 就是 false,这个操作也会被计数为 modified,因为它匹配了条件。

使用 $inc 增加数值:

javascript
// 将 name 为 "李四" 的用户的 age 字段增加 1
myapp_db> db.users.updateOne(
{ name: "李四" },
{ $inc: { age: 1 } }
)
{
acknowledged: true,
insertedId: null,
matchedCount: 1,
modifiedCount: 1,
upsertedId: null
}

现在查询 李四,他的年龄变成了 26。

使用 $push 向数组添加元素:

javascript
// 向 name 为 "王五" 的用户的 interests 数组添加 "跑步"
myapp_db> db.users.updateOne(
{ name: "王五" },
{ $push: { interests: "跑步" } }
)
{
acknowledged: true,
insertedId: null,
matchedCount: 1,
modifiedCount: 1,
upsertedId: null
}

查询 王五 的文档,会看到 interests 数组变成了 [ '跑步' ] (如果之前没有 interests 字段,会创建一个新数组;如果之前有,则添加进去)。

3.6 删除 (Delete) – 删除文档

使用 db.<collection_name>.deleteOne()db.<collection_name>.deleteMany() 方法删除文档。它们都接受一个查询文档作为参数,指定要删除哪个文档。

  • deleteOne(): 删除满足条件的 第一个 文档。
  • deleteMany(): 删除满足条件的 所有 文档。

删除单个文档 (deleteOne):

javascript
// 删除 name 为 "张三" 的用户
myapp_db> db.users.deleteOne({ name: "张三" })
{ acknowledged: true, deletedCount: 1 }

再次查询 张三,将找不到结果。

删除多个文档 (deleteMany):

javascript
// 删除 age 小于 25 的所有用户
myapp_db> db.users.deleteMany({ age: { $lt: 25 } })
{ acknowledged: true, deletedCount: 1 } // 只剩李四和王五,age小于25只有王五

再次查询所有文档,您会发现只有 李四 的文档了。

删除集合中的所有文档: 使用一个空的查询文档 {} 删除 deleteMany()

javascript
myapp_db> db.users.deleteMany({})
{ acknowledged: true, deletedCount: 1 } // 删除了最后一个文档李四

现在 users 集合为空了。

3.7 删除集合和数据库

  • 删除集合:
    javascript
    myapp_db> db.users.drop()
    true // 返回 true 表示成功

    现在 show collections 将看不到 users 集合了。

  • 删除数据库:
    javascript
    myapp_db> db.dropDatabase()
    { dropped: 'myapp_db', ok: 1 } // 返回结果表示成功

    现在 show dbs 将看不到 myapp_db 数据库了。执行 use test 回到默认数据库。

4. 索引 (Indexes)

虽然在入门阶段不是必须深入了解,但理解索引的概念及其重要性对于使用 MongoDB 至关重要。

索引的作用: 索引就像书的目录,可以极大地加快查询速度。MongoDB 在查询时如果没有合适的索引,需要扫描集合中的所有文档来查找匹配项(称为集合扫描),这在大数据量时效率极低。创建索引后,MongoDB 可以直接通过索引找到匹配文档的位置,从而快速访问。

创建索引: 使用 db.<collection_name>.createIndex() 方法。

javascript
// 在 users 集合的 name 字段上创建升序索引 (1 表示升序,-1 表示降序)
myapp_db> db.users.createIndex({ name: 1 })
{ numIndexesBefore: 1, numIndexesAfter: 2, createdCollectionAutomatically: false, ok: 1 }

_id 字段上会自动创建一个唯一的索引。所以第一次创建其他索引时,numIndexesBefore 通常是 1。

查看索引:

javascript
myapp_db> db.users.getIndexes()
[
{ v: 2, key: { _id: 1 }, name: '_id_' },
{ v: 2, key: { name: 1 }, name: 'name_1' } // 刚刚创建的索引
]

在进行频繁查询的字段上创建索引是优化 MongoDB 性能的关键步骤。

第三部分:超越基础

掌握了 CRUD 操作后,您已经迈出了 MongoDB 的第一步。但 MongoDB 的能力远不止于此。一些更高级的概念和工具包括:

  • 聚合框架 (Aggregation Framework): 一个强大的数据处理管线,可以进行过滤、分组、转换、排序、计算等复杂操作,类似于 SQL 中的 GROUP BY 和 JOIN。
  • 副本集 (Replica Sets): 配置高可用性和读写分离。
  • 分片 (Sharding): 实现水平扩展,处理超大数据集和高吞吐量。
  • 用户和角色管理: 确保数据库安全。
  • 数据库驱动 (Drivers): 在各种编程语言(如 Node.js, Python, Java, C#, PHP 等)中使用 MongoDB。
  • GUI 工具: MongoDB Compass, Robo 3T (原 Robomongo) 等可视化工具,方便查看数据、执行查询和管理数据库。

总结

MongoDB 作为一种流行的 NoSQL 文档型数据库,凭借其灵活的数据模型、易于扩展的架构和高性能的特性,在现代应用开发中扮演着越来越重要的角色。从理解其核心概念(文档、集合、数据库)和优势(灵活性、可扩展性、高性能)开始,到掌握基础的安装、连接以及 CRUD 操作,您已经具备了使用 MongoDB 构建简单应用的基础能力。

这仅仅是 MongoDB 世界的开端。要充分发挥 MongoDB 的潜力,还需要深入学习索引优化、聚合框架、副本集、分片等更高级的主题,并结合具体的应用场景进行实践。

祝您在 MongoDB 的学习和使用之路上一切顺利!现在,就开始您的 MongoDB 实践之旅吧!

发表评论

您的邮箱地址不会被公开。 必填项已用 * 标注

滚动至顶部