MongoDB入门笔记
MongoDB数据库笔记
1. 引言
1.1 什么是数据库?
- 数据库是按照数据结构来组织、存储和管理数据的仓库。
- 我们的程序都是在内存中运行的,一旦程序运行结束或者计算机断电,程序运行中的数据都会丢失。
- 所以我们就需要将一些程序运行的数据持久化到硬盘之中,以确保数据的安全性。而数据库就是数据持久化的最佳选择。
- 说白了,数据库就是存储数据的仓库。
1.2 数据库的分类
数据库主要分成两种
关系型数据库(RDBMS)
- MySQL、Oracle、DB2、SQL Server60……
- 关系数据库中全都是表
非关系型数据库
- MongoDB、Redis ......
- 键值对数据库
- 文档数据库MongoDB
1.3 什么是MongoDB?
- MongoDB是为快速开发互联网Web应用而设计的数据库系统。
- MongoDB的设计自标是极简、灵活、作为Web应用栈的一部分。
- MongoDB的数据模型是面向文档的,所谓文档是一种类似于JSON的结构,简单理解MongoDB这个数据库中存的是各种各样的JSON。(BSON)
2. 安装与配置(Windows)
步骤 1:下载 MongoDB
- 访问 MongoDB 官网: 打开浏览器,访问 MongoDB 下载页面。
Download MongoDB Community Server
选择版本:
- 选择 MongoDB Community Server。
- 在平台选项中选择 Windows。
- 确保选择适用于你系统架构的版本(通常是 MSI 安装包,默认选择 x64)。
- 点击 Download 按钮下载最新稳定版本。
步骤 2:安装 MongoDB
- 运行安装程序: 双击下载的 MSI 安装文件,开始安装向导。
选择安装类型:
- 在第一个窗口选择 Complete(完全安装),这将安装所有的 MongoDB 组件,包括 MongoDB Server等。
设置 MongoDB 服务:
- 在“Service Configuration”步骤中,确保勾选 Run MongoDB as a Service 选项,这样 MongoDB 将作为 Windows 服务自动运行。
- Network Service User 一般无需更改,保留默认即可。
- 安装完成: 点击 Install 开始安装。安装完成后,可以直接关闭安装向导。
步骤 3:配置系统环境变量
找到 MongoDB 安装路径:
- 默认安装路径为
C:\Program Files\MongoDB\Server\版本号\bin
。
- 默认安装路径为
配置环境变量:
- 右键点击桌面上的 此电脑,选择 属性。
- 点击 高级系统设置,然后选择 环境变量。
- 在 系统变量 中找到 Path,点击 编辑。
- 点击 新建,将 MongoDB 的 bin 目录路径添加进去,例如:
C:\Program Files\MongoDB\Server\6.0\bin
。 - 点击 确定 保存。
步骤 4:下载 MongoDB Shell
- 访问 MongoDB Shell 下载页面: 打开浏览器,访问 MongoDB Shell 下载页面。
选择操作系统:
- 在“Platform”选项中选择
Windows
。 - 选择适合的架构(通常是
x64
)。 - 点击 Download 按钮下载 MongoDB Shell 的最新版本。
- 在“Platform”选项中选择
步骤 5:安装 MongoDB Shell
运行安装程序:
- 找到刚刚下载的安装文件(一般是
mongosh-<版本号>-win32-x64.msi
),双击运行。
- 找到刚刚下载的安装文件(一般是
开始安装:
- 点击 Next,选择安装目录(通常保留默认路径即可)。
- 点击 Install,开始安装 MongoDB Shell。
完成安装:
- 安装完成后,点击 Finish 关闭安装程序。
步骤 6:配置系统环境变量
为了能够在任意命令行窗口中使用 mongosh
,建议将 MongoDB Shell 的安装路径添加到系统环境变量中:
找到安装路径:
- 默认安装路径为
C:\Program Files\MongoDB\mongosh\bin
。
- 默认安装路径为
配置环境变量:
- 右键点击桌面上的 此电脑,选择 属性。
- 点击 高级系统设置,然后选择 环境变量。
- 在 系统变量 中找到 Path,点击 编辑。
- 点击 新建,将 MongoDB Shell 的 bin 目录路径添加进去,例如:
C:\Program Files\MongoDB\mongosh\bin
。 - 点击 确定 保存。
步骤 7:启动 MongoDB 服务
启动 MongoDB:
打开命令提示符,输入以下命令以启动 MongoDB 服务:
net start MongoDB
启动 MongoDB Shell:
- 打开命令提示符(cmd)或 Windows Terminal。
- 输入
mongosh
并按下 Enter。
连接到本地 MongoDB 实例:
- 如果 MongoDB Server 正在运行,
mongosh
将自动连接到默认的本地实例(mongodb://localhost:27017
)。 - 成功启动后,你会看到 MongoDB Shell 的欢迎界面,并显示连接成功的信息。
- 如果 MongoDB Server 正在运行,
3. MongoDB 基础概念
3.1 文档、集合与数据库
- 数据库(database)-数据库是一个仓库,在仓库中可以存放集合。
- 集合(collection)-集合类似于数组,在集合中可以存放文档。
- 文档(document)-文档数据库中的最小单位,我们存储和操作的内容都是文档。
3.2 BSON 与 JSON 的异同
3.2.1 相似点
- 数据表示:BSON 和 JSON 都用于表示键-值对的数据结构,是常用的数据交换格式。
- 易读性:两者都支持类似的类型,如字符串、数字、数组、对象等,且容易阅读和理解。
- 用途:两者常用于应用程序之间的数据交换,尤其是在 Web 应用和数据库(如 MongoDB)之间。
3.2.2 不同点
格式:
- JSON:是基于文本的轻量数据交换格式,使用 Unicode 字符集进行编码,易于阅读和编写。
- BSON:是二进制编码的 JSON 格式,设计上为了提高数据存储和检索的效率。它更适合计算机处理。
数据类型:
- JSON:支持字符串、数字(整数和浮点数)、布尔值、数组、对象和
null
。 - BSON:除了 JSON 支持的类型外,还支持更多的数据类型,如日期、二进制数据、ObjectId(用于唯一标识文档)、布尔值、32 位和 64 位整数等。
- JSON:支持字符串、数字(整数和浮点数)、布尔值、数组、对象和
效率:
- JSON:由于是文本格式,解析速度相对较慢,占用更多的空间,尤其是在传输和处理大型数据时。
- BSON:由于是二进制格式,更高效,尤其在数据量大、对性能要求高的情况下。BSON 在编码和解码时速度更快,占用的空间更小。
可扩展性:
- JSON:由于其格式限制,无法直接存储特殊类型(如日期或二进制数据),需要进行序列化。
- BSON:设计时考虑了扩展性,可以更方便地添加新的数据类型,适合更复杂的数据结构。
使用场景:
- JSON:广泛用于网络通信、API 接口数据传输等,需要人类可读性较高的场景。
- BSON:主要用于 MongoDB 等数据库中,适用于需要高效读写性能和更复杂的数据存储场景。
3.3 数据类型与结构
MongoDB 支持多种数据类型,以适应不同的数据存储需求。以下是 MongoDB 中常用的数据类型:
3.3.1 基本数据类型
- String: 字符串。存储 UTF-8 编码的文本数据。
- Integer: 整数。可以是 32 位或 64 位,取决于服务器。
- Double: 浮点数。用于存储小数。
- Boolean: 布尔值。存储 true 或 false。
- Null: 表示空值或不存在的字段。
3.3.2 日期和时间类型
- Date: 日期时间。以 UNIX 时间格式(毫秒数)存储。
- Timestamp: 时间戳。通常用于内部操作。
3.3.3 复杂数据类型
- Array: 数组。可以存储多个值的列表。
- Object/Embedded Document: 嵌入式文档。允许在一个文档中嵌入另一个文档。
3.3.4 特殊数据类型
- ObjectId: 12 字节的唯一标识符,通常用作文档的 _id。
- Binary Data: 用于存储二进制数据(如图片、音频文件等)。
- Regular Expression: 正则表达式。用于模式匹配。
3.3.5 数字类型
- NumberInt: 32 位整数。
- NumberLong: 64 位整数。
- NumberDecimal: 高精度小数。
3.3.6 数据结构示例
以下是一个包含多种数据类型的 MongoDB 文档示例:
{
_id: ObjectId("5f8a7b2b9d3b2a1b1c1d1e1f"),
name: "John Doe",
age: NumberInt(30),
height: 175.5,
isStudent: false,
birthDate: new Date("1990-01-01"),
grades: [85, 90, 78, 92],
address: {
street: "123 Main St",
city: "New York",
zipCode: "10001"
},
profilePicture: BinData(0, "base64encodeddata..."),
tags: ["developer", "mongodb", "javascript"],
lastLogin: ISODate("2023-10-07T14:44:00Z"),
score: NumberDecimal("123.456789")
}
这个示例展示了如何在一个 MongoDB 文档中使用各种数据类型,包括基本类型、复杂类型和特殊类型。MongoDB 的灵活架构允许在同一个集合中存储具有不同结构的文档,这是它作为文档数据库的一个主要优势。
4. 基础操作
4.1 创建、查看与删除数据库
4.1.1 创建与使用数据库
当你使用 use 命令来指定一个数据库时,如果该数据库不存在,MongoDB将自动创建它。
MongoDB 创建数据库的语法格式如下:
use DATABASE_NAME
4.1.2 查看所有数据库
如果你想查看所有数据库,可以使用 show dbs
命令
> show dbs
admin 0.000GB
config 0.000GB
local 0.000GB
也可以使用show databases
命令
>show databases
admin 0.000GB
config 0.000GB
local 0.000GB
4.1.3 删除数据库
语法
MongoDB 删除数据库的语法格式:db.dropDatabase()
test> use test
already on db test
test> db.dropDatabase()
{ ok: 1, dropped: 'test' }
test> show dbs
admin 40.00 KiB
config 108.00 KiB
local 40.00 KiB
4.2 创建、更新与删除集合
4.2.1 MongoDB 创建集合
4.2.1.1 MongoDB 显式创建集合
MongoDB 中使用 createCollection()
方法来创建集合。
语法:db.createCollection(name, options)
参数说明:
- name: 要创建的集合名称。
- options: 可选参数, 指定有关内存大小及索引的选项。
options对象参数表
参数名 | 类型 | 描述 | 示例值 |
---|---|---|---|
capped | 布尔值 | 是否创建一个固定大小的集合。 | true |
size | 数值 | 集合的最大大小(以字节为单位)。仅在 capped 为 true 时有效。 | 10485760 (10MB) |
max | 数值 | 集合中允许的最大文档数。仅在 capped 为 true 时有效。 | 5000 |
validator | 对象 | 用于文档验证的表达式。 | { $jsonSchema: { ... }} |
validationLevel | 字符串 | 指定文档验证的严格程度。"off" :不进行验证。"strict" :插入和更新操作都必须通过验证(默认)。"moderate" :仅现有文档更新时必须通过验证,插入新文档时不需要。 | "strict" |
validationAction | 字符串 | 指定文档验证失败时的操作。"error" :阻止插入或更新(默认)。"warn" :允许插入或更新,但会发出警告。 | "error" |
storageEngine | 对象 | 为集合指定存储引擎配置。 | { wiredTiger: { ... }} |
collation | 对象 | 指定集合的默认排序规则。 | { locale: "en", strength: 2 } |
db.createCollection("myComplexCollection", {
capped: true,
size: 10485760,
max: 5000,
validator: { $jsonSchema: {
bsonType: "object",
required: ["name", "email"],
properties: {
name: {
bsonType: "string",
description: "必须为字符串且为必填项"
},
email: {
bsonType: "string",
pattern: "^.+@.+$",
description: "必须为有效的电子邮件地址"
}
}
}},
validationLevel: "strict",
validationAction: "error",
storageEngine: {
wiredTiger: { configString: "block_compressor=zstd" }
},
collation: { locale: "en", strength: 2 }
});
4.2.1.2 MongoDB 隐式创建集合
在MongoDB中,当你第一次向一个集合插入文档时,如果该集合不存在,MongoDB会自动创建集合:
db.<collection_name>.insert({name:"孙悟空",age:18,gender:"男"})
如果<collection_name>
集合还不存在,MongoDB会自动创建它并插入一条文档。
<aside>
💡在 MongoDB 中,集合在拥有内容之前不会真正创建!
如果尝试将文档插入不存在的集合中,MongoDB 将自动创建该集合。
</aside>
4.3 插入、查询、更新与删除文档(CRUD操作)
4.3.1 向数据库插入文档
~~db.<collection>.insert()~~
←已废弃勿用- 向集合中插入一个或多个文档
- 当我们向集合中插入文档时,如果没有给文档指定_id属性,则数据库会自动为文档添加_id
- _id我们可以自己指定,如果我们指定了数据库就不会在添加了,如果自己指定_id也必须确保它的唯一性(不建议这么做)
db.collection.insertone()
插入一个文档对象
db.collection('inventory').insertOne({ item: 'canvas', qty: 100, tags: ['cotton'], size: { h: 28, w: 35.5, uom: 'cm' } });
 插入成功
db.collection.insertMany()
插入多个文档对象,将文档数组传递给该方法

test> db.stus.find() [ { _id: ObjectId('66f96b3667ae1e5915c73bf9'), name: '孙悟空', age: 18, gender: '男' }, { _id: ObjectId('66f96b3667ae1e5915c73bfa'), name: '猪八戒', age: 28, gender: '男' }, { _id: ObjectId('66f96b3667ae1e5915c73bfb'), name: '白骨精', age: 16, gender: '女' } ] test>
4.3.2 查询文档
MongoDB 查询文档使用 find()
、findOne()
方法。
4.3.2.1 find()
语法格式:
db.collection.find(query, projection)
- query:用于查找文档的查询条件。默认为
{}
,即匹配所有文档。 - projection(可选):指定返回结果中包含或排除的字段。
- 查找所有文档:
db.myCollection.find();
4.3.2.2 findOne()
只选择一个文档,我们可以使用 findOne()
方法。
语法格式:
db.collection.findOne(query, projection)
- query:用于查找文档的查询条件。默认为
{}
,即匹配所有文档。 - projection(可选):指定返回结果中包含或排除的字段。
<aside>
💡MongoDB支持直接通过内嵌文档的属性进行查询,如果要查询内嵌文档则可以通过.
的形式来匹配如果要通过内嵌文档来对文档进行查询,此时属性名必须使用引号
</aside>
db.users.find({"hobby.movies":"hero"});
4.3.3 更新文档
Collection.updateOne(filter, update, options)
db.collection('inventory').updateOne( { item: 'paper' }, { $set: { 'size.uom': 'cm', status: 'P' }, $currentDate: { lastModified: true } } );
Collection.updateMany(filter, update, options)
db.myCollection.updateMany( { age: { $lt: 30 } }, // 过滤条件 { $set: { status: "active" } }, // 更新操作 { upsert: false } // 可选参数 );
Collection.replaceOne(filter, replacement, options)
db.myCollection.replaceOne( { name: "Bob" }, // 过滤条件 { name: "Bob", age: 31 } // 新文档 );
db.collection.findOneAndUpdate(filter, update, options)
db.myCollection.findOneAndUpdate( { name: "Charlie" }, // 过滤条件 { $set: { age: 36 } }, // 更新操作 { returnDocument: "after" } // 可选参数,返回更新后的文档 );
- filter:用于查找文档的查询条件。
- update:指定更新操作的文档或更新操作符。
- options:可选参数对象,如
upsert
、arrayFilters
等。 - replacement:新的文档,将替换旧的文档。
更新操作/字段 :
名称 说明 $currentDate
将字段的值设置为当前日期,可以是日期或时间戳。 $inc
将字段的值按指定量递增。 $min
仅当指定值小于现有字段值时才更新字段。 $max
仅当指定值大于现有字段值时才更新字段。 $mul
将字段的值乘以指定量。 $rename
重命名字段。 $set
设置文档中字段的值。 $setOnInsert
如果某一更新操作导致插入文档,则设置字段的值。对修改现有文档的更新操作没有影响。 $unset
从文档中删除指定的字段。 使用
$set
操作符将size.uom
字段的值更新为"cm"
,并将status
字段的值更新为"P"
,[$set](https://www.mongodb.com/zh-cn/docs/manual/reference/operator/update/set/#mongodb-update-up.-set)
使用
$currentDate
操作符将lastModified
字段的值更新为当前日期。如果lastModified
字段不存在,则$currentDate
将创建该字段。[$currentDate](https://www.mongodb.com/zh-cn/docs/manual/reference/operator/update/currentDate/#mongodb-update-up.-currentDate)
$unset
操作符删除特定字段。[$unset](https://www.mongodb.com/zh-cn/docs/manual/reference/operator/update/unset/#mongodb-update-up.-unset)
$inc
操作符将字段按指定值递增。[$inc](https://www.mongodb.com/zh-cn/docs/manual/reference/operator/update/inc/#mongodb-update-up.-inc)
数组操作符
名称 说明 $
充当占位符,用于更新与查询条件匹配的第一个元素。 $[]
充当占位符,以更新数组中与查询条件匹配的文档中的所有元素。 $[<identifier>]
充当占位符,以更新与查询条件匹配的文档中所有符合 arrayFilters
条件的元素。$addToSet
仅向数组中添加尚不存在于该数组的元素。 $pop
删除数组的第一项或最后一项。 $pull
删除与指定查询匹配的所有数组元素。 $push
向数组添加一项。 $pullAll
从数组中删除所有匹配值。 $push
用于向数组中添加一个新的元素[$push](https://www.mongodb.com/zh-cn/docs/manual/reference/operator/update/push/#mongodb-update-up.-push) **举例** 创建 `students` 集合:
db.students.insertOne( {_id:1,scores: [44,78,38,80 ] } )
**将值追加到数组** 以下示例向 `scores` 数组追加 `89`:
db.students.updateOne({_id:1 }, {$push: {scores:89 } })
示例输出:
{ _id: 1, scores: [ 44, 78, 38, 80, 89 ] }
---
$addToSet
向数组中添加一个新元素(若数组中已有该元素,则不会添加)如果对**不是**数组的字段使用 `$addToSet`,操作将会失败。 如果数值是一个数组,`$addToSet` 会将整个数组作为***一个***元素追加。 [$addToSet](https://www.mongodb.com/zh-cn/docs/manual/reference/operator/update/addToSet/#mongodb-update-up.-addToSet)
4.3.4 删除文档
db.collection.deleteOne(filter, options)
即使多个文档可能与指定筛选器匹配,最多也只删除与指定筛选器匹配的单个文档。db.collection.deleteMany(filter, options)
删除所有与指定筛选器匹配的文档。- filter:用于查找要删除的文档的查询条件。
- options(可选):一个可选参数对象。
db.collection.remove(<query>,<justOne>)
删除与指定过滤器匹配的单个文档或所有文档。db.collection.remove( <query>, { justOne: <boolean>, writeConcern: <document>, collation: <document>, let: <document> // Added in MongoDB 5.0 } )
db.stus.remove({})
清空集合(性能低,集合仍然在)db.stus.drop()
彻底删除集合,若该集合为数据库唯一集合,则该数据库也会被删除db.dropDatabase()
删除数据库
4.3.5 MongoDB查询文档数量
在MongoDB中,查询文档数量是一个常见的操作。以下是几种方法来实现这一功能:
1. 使用count()方法
db.collection.count()
这是最简单的方法,但在MongoDB 4.0及以后版本中已被废弃。
2. 使用countDocuments()方法
db.collection.countDocuments(query, options)
这是推荐的方法,它提供了准确的计数。
3. 使用estimatedDocumentCount()方法
db.collection.estimatedDocumentCount(options)
这个方法提供了集合的估计文档数,速度更快但可能不太准确。
4. 使用aggregate()方法
db.collection.aggregate([
{ $count: "total" }
])
这种方法可以在复杂的聚合管道中使用。
在实际应用中,选择哪种方法取决于你的具体需求,如精确度要求、查询复杂度和性能考虑等。
4.4 条件查询与排序
4.4.1 比较操作符与逻辑操作符
在MongoDB中,条件查询和排序是两个非常重要的操作,它们允许我们从数据库中检索特定的文档并按照指定的顺序呈现结果。以下是一些常用的条件查询和排序方法:
- 使用比较操作符:
| **名称** | **说明** |
| --- | --- |
| [`$eq`](https://www.mongodb.com/zh-cn/docs/manual/reference/operator/query/eq/#mongodb-query-op.-eq) | 匹配等于指定值的值。 |
| [`$gt`](https://www.mongodb.com/zh-cn/docs/manual/reference/operator/query/gt/#mongodb-query-op.-gt) | 匹配大于指定值的值。 |
| [`$gte`](https://www.mongodb.com/zh-cn/docs/manual/reference/operator/query/gte/#mongodb-query-op.-gte) | 匹配大于等于指定值的值。 |
| [`$in`](https://www.mongodb.com/zh-cn/docs/manual/reference/operator/query/in/#mongodb-query-op.-in) | 匹配数组中指定的任何值。 |
| [`$lt`](https://www.mongodb.com/zh-cn/docs/manual/reference/operator/query/lt/#mongodb-query-op.-lt) | 匹配小于指定值的值。 |
| [`$lte`](https://www.mongodb.com/zh-cn/docs/manual/reference/operator/query/lte/#mongodb-query-op.-lte) | 匹配小于等于指定值的值。 |
| [`$ne`](https://www.mongodb.com/zh-cn/docs/manual/reference/operator/query/ne/#mongodb-query-op.-ne) | 匹配所有不等于指定值的值。 |
| [`$nin`](https://www.mongodb.com/zh-cn/docs/manual/reference/operator/query/nin/#mongodb-query-op.-nin) | 不匹配数组中指定的任何值。 |
| | |
db.collection.find({ field: { $gt: value } }) // 大于
db.collection.find({ field: { $lt: value } }) // 小于
db.collection.find({ field: { $gte: value } }) // 大于等于
db.collection.find({ field: { $lte: value } }) // 小于等于
db.collection.find({ field: { $ne: value } }) // 不等于
- 使用逻辑操作符:
| **名称** | **说明** |
| --- | --- |
| [`$and`](https://www.mongodb.com/zh-cn/docs/manual/reference/operator/query/and/#mongodb-query-op.-and) | 使用逻辑 `AND` 连接查询子句将返回与两个子句的条件匹配的所有文档。 |
| [`$not`](https://www.mongodb.com/zh-cn/docs/manual/reference/operator/query/not/#mongodb-query-op.-not) | 反转查询谓词的效果并返回与查询谓词***不***匹配的文档。 |
| [`$nor`](https://www.mongodb.com/zh-cn/docs/manual/reference/operator/query/nor/#mongodb-query-op.-nor) | 使用逻辑 `NOR` 的联接查询子句会返回无法匹配这两个子句的所有文档。 |
| [`$or`](https://www.mongodb.com/zh-cn/docs/manual/reference/operator/query/or/#mongodb-query-op.-or) | 使用逻辑 `OR` 连接多个查询子句会返回符合任一子句条件的所有文档。 |
db.collection.find({ $and: [ { condition1 }, { condition2 } ] })
db.collection.find({ $or: [ { condition1 }, { condition2 } ] })
db.collection.find({ field: { $not: { $gt: value } } })
使用正则表达式:
db.collection.find({ field: /pattern/ })
4.4.2 排序
查询文档时,默认情况是按照id的值进行排列(升序)
MongoDB使用sort()
方法对查询结果进行排序。sort()
需要传递一个对象来指定排序规则,1表示升序,-1表示降序。
limit()、skip()、sort() 可以以任意的顺序进行调用
db.collection.find().sort({ field1: 1, field2: -1 })
4.4.3 复合查询示例
以下是一个结合条件查询和排序的复合查询示例:
db.users.find({ age: { $gte: 18 }, city: "New York" })
.sort({ name: 1, age: -1 })
.limit(10)
这个查询会找到所有年龄大于等于18岁且城市为New York的用户,按照姓名升序和年龄降序排列,并返回前10个结果。
4.4.4 查询优化技巧
- 使用适当的索引来加速查询
- 尽量避免使用负向条件(如$ne, $not)
- 使用投影来限制返回的字段,减少数据传输量
- 合理使用limit()和skip()来分页
通过掌握这些条件查询和排序技巧,可以更有效地从MongoDB数据库中检索和操作数据。
5. 高级查询与聚合
5.1 使用投影、限制与跳过
5.1.1 投影(Projection)
投影是MongoDB中一种强大的查询优化技术,它允许我们指定查询结果中应该返回哪些字段。通过使用投影,我们可以减少网络传输和客户端内存使用,从而提高查询效率。
基本语法
db.collection.find({}, { field1: 1, field2: 1, _id: 0 })
在这个例子中:
- 第一个参数 {} 是查询条件,这里是空的,表示匹配所有文档。
- 第二个参数是投影对象,指定了要返回的字段。
- 1 表示包含该字段,0 表示排除该字段。
- 默认情况下,_id 字段总是被包含,除非明确指定 _id: 0。
注意事项
- 在同一个投影对象中,不能混合使用包含和排除(除了 _id 字段)。
- 对于嵌套文档,可以使用点表示法指定嵌套字段。
高级用法
投影还支持一些高级操作符,如:
- $slice:限制返回数组字段的元素数量。
- $elemMatch:在数组字段中匹配特定条件的元素。
- $:返回数组中匹配查询条件的第一个元素。
示例:
db.collection.find(
{ },
{
name: 1,
"address.city": 1,
comments: { $slice: 2 },
_id: 0
}
)
这个查询会返回每个文档的 name 字段、address 中的 city 字段,以及 comments 数组的前两个元素,同时排除 _id 字段。
通过合理使用投影,我们可以显著优化查询性能,特别是在处理大型文档或大量数据时。
5.1.2 & 5.1.3 限制(Limit)、跳过(Skip)
limit()
- 在游标上使用
limit()
方法,可以指定游标返回的文档的最大数量。limit()
类似于 SQL 数据库中的LIMIT
语句。—> limit()方法用于限制返回结果的数量。
db.collection.find().limit(5)
这将返回集合中的前5个文档。
- 在游标上使用
skip()
- skip()方法用于跳过指定数量的文档。
db.collection.find().skip(10)
这将跳过前10个文档,从第11个文档开始返回结果。
限制与跳过同时使用(如小练习25题)
查看numbers集合中的第21条到30条数据
db.numbers.find().skip(20).limit(10); //valid db.numbers.find().limit(10).skip(10); //alse valid
MongoDB会自动调整limit()与skip()的顺序,故两者效果相同
5.2 聚合管道与常见的聚合操作
MongoDB的聚合管道是一个强大的数据处理工具,允许我们对文档进行复杂的转换和分析。它由多个阶段组成,每个阶段对输入执行特定操作,并将结果传递给下一个阶段。以下是一些常见的聚合操作和它们的用途:
- $match:用于过滤文档,类似于find()操作。
- $group:根据指定的表达式对文档进行分组,并可能应用累加器。
- $sort:对文档进行排序。
- $project:用于选择、重命名或添加字段。
- $limit**和**$skip:用于分页操作。
- $unwind:将数组字段拆分为多个文档。
聚合管道的优势在于它可以在服务器端高效地处理大量数据,减少网络传输和客户端处理的负担。以下是一个简单的聚合管道示例:
db.orders.aggregate([
{ $match: { status: "completed" } },
{ $group: { _id: "$customer", total: { $sum: "$amount" } } },
{ $sort: { total: -1 } },
{ $limit: 5 }
])
这个例子首先匹配所有已完成的订单,然后按客户分组并计算总金额,接着按总金额降序排序,最后返回前5个结果。通过这种方式,我们可以轻松地执行复杂的数据分析任务。
5.3 MapReduce概述
MapReduce是MongoDB中用于复杂数据处理和聚合的编程模型。它允许开发者通过自定义JavaScript函数来处理大量数据。虽然聚合管道在大多数情况下更为高效和易用,但MapReduce仍然在某些复杂场景中发挥作用。
MapReduce的工作原理
MapReduce过程主要包含三个阶段:
- Map阶段: 对每个输入文档应用map函数,生成键值对。这个阶段的主要目的是将数据转换为可以并行处理的形式。
- Shuffle阶段: MongoDB自动将具有相同键的值分组。这是一个中间阶段,为reduce阶段准备数据。
- Reduce阶段: 对每组键值对应用reduce函数,进行汇总或复杂计算。这个阶段合并map阶段的结果,生成最终输出。
MapReduce的详细过程
- 输入: 首先,MongoDB从指定的集合中读取文档。
- Map函数: 对每个文档应用自定义的map函数。这个函数可以解析文档,提取需要的字段,并输出键值对。
- 中间结果: Map函数的输出被临时存储。
- 分组: MongoDB将具有相同键的所有值分组。
- Reduce函数: 对每组数据应用自定义的reduce函数。这个函数接收键和该键对应的所有值,执行汇总或其他复杂操作。
- 最终结果: Reduce函数的输出被存储或返回。
MapReduce示例
假设我们有一个订单集合,想要计算每个客户的总订单金额:
var mapFunction = function() {
emit(this.customer, this.amount);
};
var reduceFunction = function(key, values) {
return Array.sum(values);
};
db.orders.mapReduce(
mapFunction,
reduceFunction,
{ out: "order_totals" }
)
在这个例子中,map函数为每个订单发出客户和金额,reduce函数则汇总每个客户的所有订单金额。
MapReduce的优势
- 灵活性: 可以执行复杂的数据转换和计算,适用于聚合管道难以处理的场景。
- 可扩展性: 适用于大规模数据处理,可以分布式执行,能够处理超出单台服务器内存限制的数据集。
- 自定义逻辑: 允许使用JavaScript编写自定义的map和reduce函数,提供了极大的灵活性。
- 增量更新: 支持增量MapReduce,可以只处理新增或修改的数据。
MapReduce的局限性
- 性能: 对于简单查询,性能可能不如聚合管道。MapReduce涉及JavaScript执行和中间结果的存储,可能导致额外的开销。
- 复杂性: 编写和调试MapReduce函数可能比聚合管道更复杂,尤其是对于不熟悉函数式编程的开发者。
- 实时性: 不适合需要实时结果的场景,因为MapReduce通常是批处理操作。
- 资源消耗: MapReduce可能比其他操作消耗更多的系统资源,特别是在处理大量数据时。
何时使用MapReduce
尽管MapReduce在某些场景下仍然有用,但MongoDB官方建议优先使用聚合管道,因为它提供了更好的性能和更简单的API。只有在以下情况下才考虑使用MapReduce:
- 需要执行聚合管道无法实现的复杂操作。
- 处理的数据量超出了聚合管道的限制。
- 需要在处理过程中执行任意的JavaScript代码。
- 需要对MapReduce结果进行增量更新。
总的来说,MapReduce是一个强大但复杂的工具,适用于特定的高级数据处理场景。在使用时,需要权衡其灵活性和潜在的性能影响。
小练习
- 进入my_test数据库
- 向数据库的user集合中插入一个文档
- 查询user集合中的文档
- 向数据库的user集合中插入一个文档
- 查询数据库user集合中的文档
- 统计数据库user集合中的文档数量
- 查询数据库user集合中username为sunwukong的文档]
- 向数据库user集合中的username为sunwukong的文档,添加一个address属性,属性值为huaguoshan
- 使用{username:"tangseng"}替换username 为zhubajie的文档
- 删除username为sunwukong的文档的address属性
- 向username为sunwukong的文档中,添加一个
hobby:{cities:["beijing","shanghai","shenzhen"], movies:["sanguo","hero"]}
- 向username为tangseng的文档中,添加一个
hobby:{movies:["A chinese odyssey","king of comedy"]}
- 查询喜欢电影hero的文档
- 向tangseng中添加一个新的电影Interstellar
- 删除喜欢beijing的用户
- 删除user集合
- 向numbers中插入20000条数据
- 查询numbers中num为500的文档
- 查询numbers中mum大于5000的文档
- 查询numbers中num小于30的文档
- 查询numbers中num大于40小于50的文档
- 查询numbers中num大于19996的文档
- 查看numbers集合中的前10条数据
- 查看numbers集合中的第11条到20条数据
- 查看numbers集合中的第21条到30条数据
6. 索引与性能优化
6.1 索引的类型与创建
MongoDB支持多种类型的索引,每种类型都有其特定的用途和优势。以下是常见的索引类型及其创建方法:
6.1.1 单字段索引
最基本的索引类型,在单个字段上创建索引。
db.collection.createIndex({ fieldName: 1 }) // 1 表示升序,-1 表示降序
6.1.2 复合索引
在多个字段上创建的索引,适用于需要同时查询多个字段的情况。
db.collection.createIndex({ field1: 1, field2: -1 })
6.1.3 多键索引
用于索引数组字段中的每个元素。MongoDB会自动为数组字段创建多键索引。
db.collection.createIndex({ arrayField: 1 })
6.1.4 文本索引
支持对字符串内容进行文本搜索。每个集合只能有一个文本索引。
db.collection.createIndex({ fieldName: "text" })
6.1.5 地理空间索引
用于支持地理空间查询。包括2d索引和2dsphere索引。
db.collection.createIndex({ location: "2dsphere" })
6.1.6 哈希索引
基于字段值的哈希来创建索引,主要用于分片。
db.collection.createIndex({ fieldName: "hashed" })
创建索引时,可以指定额外的选项,如唯一性约束、部分索引、TTL索引等。例如:
// 创建唯一索引
db.collection.createIndex({ email: 1 }, { unique: true })
// 创建部分索引
db.collection.createIndex(
{ age: 1 },
{ partialFilterExpression: { age: { $gt: 18 } } }
)
// 创建TTL索引(文档在一定时间后自动过期)
db.collection.createIndex({ createdAt: 1 }, { expireAfterSeconds: 3600 })
选择合适的索引类型和创建策略对于优化查询性能至关重要。在创建索引时,需要考虑查询模式、数据分布和写入性能等因素。
6.2 索引优化与查询性能调优
索引优化是提高MongoDB查询性能的关键。以下是一些重要的索引优化策略和查询性能调优技巧:
6.2.1 选择合适的索引类型
- 单字段索引: 适用于频繁查询单个字段的情况。
- 复合索引: 用于多字段查询,注意字段顺序对性能的影响。
- 多键索引: 适用于数组字段,但要注意可能导致索引膨胀。
- 文本索引: 用于全文搜索,但会增加存储开销。
6.2.2 避免过度索引
- 过多索引会影响写入性能和存储空间。
- 定期检查和删除未使用的索引。
- 使用数据库分析工具识别低效索引。
6.2.3 使用覆盖索引
- 创建包含查询条件和投影字段的索引,减少文档访问。
- 使用 explain() 命令验证查询是否使用了覆盖索引。
6.2.4 索引前缀
- 利用复合索引的前缀匹配特性优化查询。
- 将高选择性的字段放在复合索引的前面。
6.2.5 查询优化技巧
- 限制结果集大小: 使用 limit() 和 skip() 进行分页。
- 投影: 只返回必要的字段,减少数据传输。
- 排序优化: 创建支持排序操作的索引。
- 避免在大型集合上使用skip(): 考虑使用范围查询替代。
6.2.6. 使用查询分析工具
- explain(): 分析查询执行计划,识别性能瓶颈。
- profiler: 监控和记录慢查询,帮助优化。
- MongoDB Compass: 可视化工具,用于索引和查询分析。
通过综合运用这些策略,可以显著提高MongoDB的查询性能。记住要根据实际应用场景和数据特征来调整优化策略。
6.3 Profiling工具与查询分析
MongoDB提供了强大的Profiling工具,用于分析和优化查询性能。以下是Profiling工具的详细介绍和使用方法:
6.3.1 启用Profiling
MongoDB的Profiling系统有三个级别:
- 0级: 默认设置,不收集任何数据。
- 1级: 只收集慢查询数据。可以通过设置阈值来定义慢查询。
- 2级: 收集所有操作的数据。注意:这可能会对性能产生影响。
启用Profiling的命令:
db.setProfilingLevel(1, { slowms: 100 }) // 设置1级,慢查询阈值为100毫秒
6.3.2 查看Profiling结果
Profiling数据存储在system.profile集合中。可以使用以下命令查看:
db.system.profile.find().pretty()
每个文档包含以下关键信息:
- ts: 操作的时间戳
- op: 操作类型(查询、插入、更新等)
- millis: 操作耗时(毫秒)
- ns: 操作的命名空间(数据库和集合)
- query: 查询语句
- nreturned: 返回的文档数量
- responseLength: 响应的字节大小
6.3.3 分析慢查询
要找出最慢的查询,可以使用以下命令:
db.system.profile.find({ millis: { $gt: 100 } }).sort({ millis: -1 })
这将返回所有执行时间超过100毫秒的查询,按耗时降序排列。
6.3.4 使用explain()分析查询
对于特定查询,可以使用explain()方法来获取详细的执行计划:
db.collection.find({ field: value }).explain("executionStats")
这将提供查询的执行统计,包括索引使用情况、文档扫描数等信息。
6.3.5 优化建议
- 创建索引: 根据慢查询分析结果,为频繁查询的字段创建适当的索引。
- 限制返回字段: 使用投影来只返回必要的字段,减少数据传输量。
- 使用限制和跳过: 合理使用limit()和skip()来分页,避免大量数据传输。
- 优化查询模式: 重写复杂查询,尽量利用索引。
通过综合运用这些Profiling工具和技术,可以有效识别和解决MongoDB中的性能问题,从而显著提高数据库的整体性能。
7. MongoDB 数据模型设计
7.1 模型设计原则
在进行数据库模型设计时,无论是关系型数据库还是像 MongoDB 这样的 NoSQL 数据库,都有一系列通用的设计原则。这些原则旨在确保数据模型具有高效性、可扩展性、易维护性和良好的性能。下面详细介绍模型设计的关键原则:
需求驱动设计
数据库模型设计应根据业务需求进行,而非先假设一个通用模型再去适应需求。了解应用的业务场景,分析要存储的具体数据、查询频率、查询类型以及更新频率等因素。
- 分析需求:理解系统的核心功能以及系统需要解决的问题。例如:数据的读写比率、事务的复杂性、查询的延迟要求。
- 关注用户行为模式:确保模型能高效支持常用查询和访问模式,例如高频查询的优化。
规范化(Normalization)与反规范化(Denormalization)
规范化是数据库设计中常用的技术,目的是将数据分解到多个表中,以减少数据冗余、消除数据更新时的异常现象,并保证数据的一致性。然而,规范化的设计可能会导致查询变得复杂,需要多次关联(Join)查询,影响性能。
反规范化是针对规范化设计的性能问题采取的优化方法。反规范化意味着通过增加数据冗余或嵌入来减少查询时的关联操作,提升读性能,但也增加了维护和数据更新的复杂性。
- 规范化原则:设计尽可能消除数据冗余,减少数据更新的异常现象,如第一范式(1NF)、第二范式(2NF)和第三范式(3NF)。
- 反规范化适用场景:当读操作频率较高,且查询性能至关重要时,可以考虑反规范化,适当容忍数据冗余以换取读性能的提升。
数据一致性与完整性
确保模型设计时的数据一致性,特别是在关系数据库中,使用外键约束、唯一性约束等手段。对于 NoSQL 数据库,数据一致性通常由应用层控制,设计时需要考虑如何处理分布式环境下的数据一致性问题。
- 强一致性:某些系统(如金融系统)对数据一致性要求极高,这时可以使用 ACID 特性强的数据库,或通过事务支持来保证一致性。
- 最终一致性:对于高并发、分布式系统,可选择最终一致性模型,允许数据在短时间内不一致,但最终会达到一致状态。
高效的读写操作
数据库模型应能高效地支持读写操作,特别是在数据量大的情况下,应该尽量减少查询的复杂性以及写入的锁定等待。
- 优化读操作:设计时应考虑到查询模式,尽量使模型简洁高效。使用索引优化查询,避免大规模全表扫描。
- 优化写操作:尽量减少复杂的事务和锁定冲突,确保写入时的高效性。
在 MongoDB 等 NoSQL 数据库中,嵌入文档就是一种高效读取设计,特别是当关联数据需要一起被查询时。
扩展性
设计的数据模型应该易于扩展,以适应将来数据规模的增长和业务需求的变化。
- 水平扩展(Horizontal Scaling):对于 NoSQL 系统,通常考虑水平扩展。数据应该易于分片(Sharding),并能在多节点集群中分布存储。
- 垂直扩展(Vertical Scaling):对于关系型数据库,通常采用垂直扩展方式,通过升级硬件(如更大的 CPU、内存)来支撑系统扩展。
冗余与缓存
通过冗余来提高性能和容错性,尤其在查询频繁的场景下,可以通过反规范化、嵌入文档或使用缓存机制来减少查询延迟。
- 冗余:冗余设计可以减少高频的查询计算,但需要权衡好数据更新时的一致性问题。
- 缓存:合理使用缓存(如 Redis、Memcached 等),可以极大提高读取速度。数据模型中,考虑将一些数据放入缓存,避免频繁查询数据库。
事务性和一致性策略
对于有事务要求的系统(如银行、金融类应用),确保数据模型设计支持事务特性(ACID)。对于 NoSQL 数据库,事务支持往往较弱,需要考虑如何在应用层保证事务的一致性。
- ACID:关系数据库通常支持 ACID(原子性、一致性、隔离性、持久性)事务,确保数据一致性。
- BASE:NoSQL 数据库通常采用 BASE 模型(基本可用、软状态、最终一致性),适合分布式系统,通过最终一致性保证高可用性。
使用索引
索引是提高查询性能的关键之一。设计时应仔细选择哪些字段需要创建索引。过多的索引会影响写性能,而过少的索引则会导致查询效率低下。
- 适当使用复合索引:对多个查询条件组合的字段使用复合索引,以避免多个单字段索引的无效组合。
- 避免过多索引:每个索引会消耗存储空间并影响写入性能,因此应平衡索引的数量和查询的性能。
数据模型的灵活性
尤其是在 NoSQL 系统中,数据模型应该保持灵活,以便能够适应未来的需求变化。NoSQL 中的模式是灵活的,这可以让你根据需要动态地添加字段,而不需要重新设计整个数据库。
- Schema-less:MongoDB 等数据库允许动态模式设计,初期可以设计较为宽松的结构,在业务发展过程中根据需要增加字段。
- 模式演化:随着业务增长和变化,数据模型也应允许灵活演化,尽量减少变动带来的影响。
分片与分区
当数据量增长到一定规模时,分片或分区(Sharding/Partitioning)是解决单个节点性能瓶颈的常见方法。
- 分片:MongoDB 允许水平分片,将数据分散到多个节点上,以减少单个节点的存储和查询压力。
- 分区:关系数据库可以通过分区技术,将大表拆分成多个物理块,减少查询扫描的数据量。
安全性
确保设计的数据模型具备必要的安全措施,包括对敏感数据进行加密,限制数据的访问权限。
- 访问控制:通过角色和权限机制控制数据的访问权限,确保只有授权的用户或服务可以访问敏感数据。
- 加密:对于敏感信息,考虑在存储和传输中使用加密技术,确保数据的机密性。
7.2 嵌入文档与引用文档
MongoDB 中的嵌入文档(Embedded Documents)和引用文档(Referenced Documents)是两种不同的方式来组织和存储关联数据。理解它们的优缺点以及适用场景对于设计高效的 MongoDB 数据模型非常重要。下面分别详细讲解这两种方式。
一、嵌入文档(Embedded Documents)
定义:
嵌入文档指的是在一个文档内部嵌套另一个文档。通过嵌入,将关联的数据存储在同一个文档中,而不是分成多个文档和集合。
示例:
假设我们有一个博客系统,posts
集合存储文章信息,而每篇文章都有多个评论。可以将评论嵌入到文章文档中:
{
"_id": 1,
"title": "MongoDB介绍",
"author": "张三",
"content": "这是文章内容...",
"comments": [
{
"user": "李四",
"comment": "很有帮助!",
"date": "2024-01-01"
},
{
"user": "王五",
"comment": "谢谢分享!",
"date": "2024-01-02"
}
]
}
优点:
- 高效的读操作:嵌入文档将相关数据存储在同一个文档中,因此当你读取这个文档时,可以一次性获得所有关联数据,减少了查询次数。
- 简化的数据模型:没有必要创建多个集合或使用复杂的查询和联合操作。所有相关数据存储在同一位置,使数据模型更简洁。
- 原子性:MongoDB 对单个文档内的操作是原子的。如果你对嵌入的评论进行更新,它是与文章作为一个整体被处理的,保证一致性。
缺点:
- 文档尺寸限制:MongoDB 单个文档的大小限制为 16MB。对于非常大的嵌入文档,可能会超出这个限制,尤其是当嵌入数组或大块数据时。
- 冗余数据:如果有很多相同的嵌入数据,在不同的文档中存储重复信息会导致数据冗余和空间浪费。例如,如果多个文章都嵌入了同样的作者信息。
- 更新效率低下:嵌入文档中的数据发生变化时,整个文档可能需要重新写入,这在文档比较大的时候,可能会影响性能。
适用场景:
- 关联数据经常一起读取的场景,例如文章和评论、订单和订单项等。
- 数据之间有明确的父子关系,例如一个用户有多个地址等。
二、引用文档(Referenced Documents)
定义:
引用文档是通过引用的方式在文档之间创建关联,而不是将数据嵌入到同一个文档中。引用可以通过 _id
来实现,类似于关系数据库中的外键。
示例:
在博客系统中,评论可以存储在独立的 comments
集合中,每篇文章通过 _id
引用相关的评论:
posts
集合:
{
"_id": 1,
"title": "MongoDB介绍",
"author": "张三",
"content": "这是文章内容...",
"comments": [1001, 1002] // 引用了评论文档的 ID
}
comments
集合:
{
"_id": 1001,
"user": "李四",
"comment": "很有帮助!",
"date": "2024-01-01",
"post_id": 1
}
{
"_id": 1002,
"user": "王五",
"comment": "谢谢分享!",
"date": "2024-01-02",
"post_id": 1
}
优点:
- 灵活性:引用文档不受单个文档大小限制,允许存储大量相关数据。特别适合一对多或多对多的关系,例如用户与订单、文章与评论等。
- 减少冗余:引用方式可以避免在多个文档中重复存储相同的数据。例如,多个文档可以共享同一个作者信息,而不需要复制数据。
- 独立更新:由于数据是分开存储的,更新时可以只更新需要的数据,而无需处理整个大文档。这对性能有正面影响,尤其在高并发更新场景下。
缺点:
- 查询复杂性:获取一个文档的同时,还需要查询关联的文档。这通常需要多次查询,增加了数据库操作的复杂性和响应时间。MongoDB 不支持直接的表连接(Join),所以你可能需要在应用层处理这些逻辑。
- 数据一致性问题:由于数据存储在不同的集合中,必须确保在应用层处理一致性,避免引用丢失或失效。
- 性能问题:在引用关系复杂或者数据量大的情况下,频繁的多次查询会影响性能,特别是在深度嵌套或者多个集合关联的情况下。
适用场景:
- 数据之间是弱关联关系,不需要每次都一起加载。例如,文章和评论之间可以通过懒加载的方式来获取评论。
- 数据量较大或者文档嵌套层次很深,无法通过嵌入方式存储的场景。
- 多对多的关系,或者多个集合共享同一数据的场景,如用户和角色。
三、总结与选择
嵌入文档适合:
- 关联数据关系密切且需要频繁一起查询的场景。
- 父子关系明确的情况,数据更新时需要原子性操作。
引用文档适合:
- 数据之间的关系较为松散,或者需要灵活的独立存储。
- 需要避免文档过大,或有复杂的多对多、多层嵌套关系时。
选择嵌入或引用文档的关键在于根据实际应用场景中的查询频率、数据关系紧密度、文档大小等因素来决定。嵌入适合“读取优化”,而引用则更适合“更新优化”或“存储优化”。在很多场景下,还可以结合两者使用,例如在一些小数据使用嵌入,大数据或复杂关系使用引用的混合模式。
7.3 MongoDB关系(文档间的关系)
7.3.1 一对一(1:1)
- 使用嵌入式文档描述连接的数据之间的一对一关系
可以使用嵌入式一对一模型描述以下几对关系:
- 国家与首都
- 丈夫与妻子
- 用户帐户到电子邮件地址
- 构建地址
例子:
{ _id: "joe", name: "Joe Bookreader", address: { street: "123 Fake Street", city: "Faketon", state: "MA", zip: "12345" } }
7.3.2 一对多(1: N) / 多对一(N: 1)
7.3.2.1 嵌入式文档模式
- 也能使用嵌入式文档描述连接的数据之间的一对多关系
可以使用嵌入式一对多模型描述以下关系:
- 国家与主要城市的关系
- 用户与订单的关系
- 文章与评论
例子:示例模式包含三个实体,其中的 address one
和 address two
属于同一个 patron
:
// patron document
{
_id: "joe",
name: "Joe Bookreader"
}
// address one
{
street: "123 Fake Street",
city: "Faketon",
state: "MA",
zip: "12345"
}
// address two
{
street: "1 Some Other Street",
city: "Boston",
state: "MA",
zip: "12345"
}
应用程序需要在单个页面上显示patron
和两个address
对象的信息。要允许应用程序通过单个查询检索所有必要信息,请将address one
和address two
信息嵌入到patron
文档中:
{
"_id": "joe",
"name": "Joe Bookreader",
"addresses": [
{
"street": "123 Fake Street",
"city": "Faketon",
"state": "MA",
"zip": "12345"
},
{
"street": "1 Some Other Street",
"city": "Boston",
"state": "MA",
"zip": "12345"
}
]
}
7.3.2.2 引用模式
- 使用文档之间的引用来描述连接数据之间的一对多关系
例子:将出版商文档嵌入图书文档会导致出版商数据重复,如以下文档所示:
{
title: "MongoDB: The Definitive Guide",
author: [ "Kristina Chodorow", "Mike Dirolf" ],
published_date: ISODate("2010-09-24"),
pages: 216,
language: "English",
publisher: {
name: "O'Reilly Media",
founded: 1980,
location: "CA"
}
}
{
title: "50 Tips and Tricks for MongoDB Developer",
author: "Kristina Chodorow",
published_date: ISODate("2011-05-06"),
pages: 68,
language: "English",
publisher: {
name: "O'Reilly Media",
founded: 1980,
location: "CA"
}
}
为避免出现重复的出版商数据,应使用引用并将出版商信息保存在图书集合之外的单独集合中。
使用引用时,关系的增长将决定引用的存储方式。如果每个出版商的图书数量较少且增长有限,则将图书引用存储在出版商文档中有时可能十分有用。相反,当每个出版商的图书数量没有限制时,此数据模型将导致可变且不断增长的数组,如以下示例所示:
{
name: "O'Reilly Media",
founded: 1980,
location: "CA",
books: [123456789, 234567890, ...]
}
{
_id: 123456789,
title: "MongoDB: The Definitive Guide",
author: [ "Kristina Chodorow", "Mike Dirolf" ],
published_date: ISODate("2010-09-24"),
pages: 216,
language: "English"
}
{
_id: 234567890,
title: "50 Tips and Tricks for MongoDB Developer",
author: "Kristina Chodorow",
published_date: ISODate("2011-05-06"),
pages: 68,
language: "English"
}
为避免出现可变且不断增长的数组,请将出版商的引用存储在图书文档中:
{
_id: "oreilly",
name: "O'Reilly Media",
founded: 1980,
location: "CA"
}
{
_id: 123456789,
title: "MongoDB: The Definitive Guide",
author: [ "Kristina Chodorow", "Mike Dirolf" ],
published_date: ISODate("2010-09-24"),
pages: 216,
language: "English",
publisher_id: "oreilly"
}
{
_id: 234567890,
title: "50 Tips and Tricks for MongoDB Developer",
author: "Kristina Chodorow",
published_date: ISODate("2011-05-06"),
pages: 68,
language: "English",
publisher_id: "oreilly"
}
7.3.3 多对多(N: N)
- 使用嵌入式文档描述相连数据之间的多对多关系
可以使用嵌入式多对多模型描述以下关系:
- 学生与老师
- 演员到电影
- 医生对患者
- 例子:应用程序需要在单个页面上显示书籍和作者对象的信息。要允许应用程序通过单个查询检索所有必要信息,请将作者信息嵌入相应的图书文档:
{
_id: "book001",
title: "Cell Biology",
authors: [
{
author_id: "author124",
name: "Ellie Smith"
},
{
author_id: "author381",
name: "John Palmer"
}
]
}
{
_id: "book002",
title: "Organic Chemistry",
authors: [
{
author_id: "author290",
name: "Jane James"
},
{
author_id: "author381",
name: "John Palmer"
}
]
}
8. 总结与扩展阅读
8.1 MongoDB最佳实践
a. 使用索引提升查询性能
- 在 MongoDB 中,索引是加速查询的关键。为频繁查询的字段创建索引,能够显著提高查询效率。
- 常见的索引包括单字段索引、多字段复合索引和文本索引。在设计时,尽量避免为低选择性字段(如布尔值)创建索引,以防资源浪费。
b. 数据模型设计
嵌套文档 (Embedding) 和 引用 (Referencing) 是设计 MongoDB 数据模型的两种常见模式。
- 嵌套文档适合存储关系紧密的数据,但可能会导致文档变得过大。
- 引用则适合关系较松散的数据,不过每次查询需要多次访问数据库。
- 在做出设计选择时,需要权衡性能和数据一致性。
c. 有效使用聚合框架
- MongoDB 的聚合框架提供了丰富的查询和数据处理能力,类似于 SQL 中的
GROUP BY
和JOIN
操作。 - 使用聚合框架时,可以通过流水线的方式对数据进行过滤、分组、排序和计算。
d. 数据分片
- 对于大规模数据集,分片(Sharding)是 MongoDB 中提升扩展性的一种方式。它通过将数据分布到多个服务器上来提升性能。
- 在决定分片键时,需确保选择的键能有效地将数据均匀分布在多个分片中,否则可能会导致某些分片的负载过高。
e. 数据备份与恢复
- 定期进行数据备份是确保数据安全的关键。MongoDB 提供了多种备份工具,如
mongodump
和mongorestore
,也支持快照和集群复制来保护数据。
8.2 官方文档与社区资源
官方文档:MongoDB 官方文档是学习和解决问题的最可靠来源。文档涵盖了从安装、配置到高级功能的各个方面。
[MongoDB Documentation](https://www.mongodb.com/zh-cn/docs/)
MongoDB 社区:
- MongoDB 拥有一个活跃的社区,开发者可以在论坛、Stack Overflow 和 GitHub 上参与讨论和提交问题。
- MongoDB University 提供了免费和付费的在线课程,帮助开发者快速上手和深入学习 MongoDB。
博客与教程:
- MongoDB 官方博客以及第三方技术博客经常发布有关 MongoDB 的最佳实践、性能优化、版本更新的相关文章。
开源项目:
- GitHub 上有很多与 MongoDB 相关的开源项目,开发者可以通过阅读开源代码来学习其他人如何使用 MongoDB,或者贡献自己的代码。
8.3 进一步学习路线
8.3.1 高级数据分析与机器学习集成
- 数据湖集成: 学习如何将MongoDB与数据湖解决方案集成,处理海量数据。
- 实时分析: 掌握使用MongoDB进行实时数据分析的技术。
- 机器学习管道: 了解如何将MongoDB作为机器学习工作流的一部分。
8.3.2 跨平台和移动开发
- MongoDB Realm: 学习使用MongoDB Realm进行跨平台和移动应用开发。
- 离线同步: 掌握处理离线数据和同步策略的技能。
- 边缘计算: 了解如何在边缘设备上使用MongoDB。
8.3.3 高级运维和自动化
- 容器化: 学习在Docker和Kubernetes环境中部署和管理MongoDB。
- Infrastructure as Code: 掌握使用Terraform等工具自动化MongoDB基础设施。
- CI/CD集成: 了解如何将MongoDB操作集成到持续集成和部署流程中。
8.3.4 贡献开源和社区参与
- 贡献代码: 学习如何为MongoDB开源项目贡献代码。
- 文档编写: 参与MongoDB文档的改进和翻译工作。
- 社区建设: 组织或参与MongoDB用户组和技术会议。
附练习答案
操作 1-10:
进入
my_test
数据库:use my_test
向
user
集合中插入一个文档:db.user.insertOne({ username: "zhubajie", age: 35 })
查询
user
集合中的文档:db.user.find()
再向
user
集合中插入一个文档:db.user.insertOne({ username: "sunwukong", age: 500 })
查询
user
集合中的所有文档:db.user.find()
统计
user
集合中的文档数量:db.user.countDocuments()
查询
user
集合中username
为sunwukong
的文档:db.user.find({ username: "sunwukong" })
向
username
为sunwukong
的文档中添加address
属性:db.user.updateOne({ username: "sunwukong" }, { $set: { address: "huaguoshan" } })
用
{username: "tangseng"}
替换username
为zhubajie
的文档:db.user.updateOne({ username: "zhubajie" }, { $set: { username: "tangseng" } })
删除
username
为sunwukong
的文档中的address
属性:db.user.updateOne({ username: "sunwukong" }, { $unset: { address: "" } })
操作 11-17:
向
username
为sunwukong
的文档中添加hobby
属性:db.user.updateOne( { username: "sunwukong" }, { $set: { hobby: { cities: ["beijing", "shanghai", "shenzhen"], movies: ["sanguo", "hero"] } } } )
向
username
为tangseng
的文档中添加hobby
属性:db.user.updateOne( { username: "tangseng" }, { $set: { hobby: { movies: ["A chinese odyssey", "king of comedy"] } } } )
查询喜欢电影
hero
的文档:db.user.find({ "hobby.movies": "hero" }).pretty()
向
tangseng
文档中添加一个新的电影Interstellar
:db.user.updateOne( { username: "tangseng" }, { $push: { "hobby.movies": "Interstellar" } } )
删除喜欢
beijing
的用户:db.user.deleteMany({ "hobby.cities": "beijing" })
删除
user
集合:db.user.drop()
向
numbers
集合中插入 20000 条数据:for (let i = 1; i <= 20000; i++) { db.numbers.insertOne({ num: i }); }
操作 18-24:
查询
numbers
中num
为 500 的文档:db.numbers.find({ num: 500 })
查询
numbers
中num
大于 5000 的文档:db.numbers.find({ num: { $gt: 5000 } })
查询
numbers
中num
小于 30 的文档:db.numbers.find({ num: { $lt: 30 } })
查询
numbers
中num
大于 40 且小于 50 的文档:db.numbers.find({ num: { $gt: 40, $lt: 50 } })
查询
numbers
中num
大于 19996 的文档:db.numbers.find({ num: { $gt: 19996 } })
查看
numbers
集合中的前 10 条数据:db.numbers.find().limit(10)
查看
numbers
集合中的第 11 条到第 20 条数据:db.numbers.find().skip(10).limit(10)
查看
numbers
集合中的第 21 条到第 30 条数据:db.numbers.find().skip(20).limit(10)