什么是MongoDB
文档数据库: MongoDB 使用BSON 格式的文档来存储数据,而不是传统的关系型数据库中的行和列,文档可以包含复杂的数据结构,如嵌套的数组和子文档
集合: 文档被组织在集合中,类似于关系型数据库中的表,集合中的文档可以具有不同的结构
数据库: MongoDB 允许在同一实例上创建多个数据库,每个数据库中可以包含多个集合
增
insertOne
insertOne 方法用于向集合中插入单个文档
db.collection.insertOne(document, options)
document: 要插入的文档对象
options: 可选的配置选项,例如 writeConcern 和 bypassDocumentValidation
示例:
db.users.insertOne({
name: "Alice",
age: 30,
email: "[email protected]"
});
参数解析:
document: 这里是插入的文档 { name: “Alice”, age: 30, email: “[email protected]” }
options: 可以设置 writeConcern(例如 { w: “majority”, wtimeout: 5000 })来定义写操作的确认级别,或者设置 bypassDocumentValidation 来绕过文档验证
使用技巧:
- 在插入前可以使用 findOne 或 countDocuments 来检查是否存在相同的文档,避免重复插入
insertMany
insertMany 方法用于向集合中插入多个文档
db.collection.insertMany(documents, options)
documents: 要插入的文档数组
options: 可选的配置选项,例如 ordered 和 writeConcern
示例:
db.users.insertMany([
{
name: "Bob", age: 25, email: "[email protected]" },
{
name: "Charlie", age: 35, email: "[email protected]" }
]);
参数解析:
documents: 一个包含多个文档的数组
options:
- ordered: 布尔值,指定插入文档的顺序,如果设置为 true(默认),MongoDB 按顺序插入文档,遇到错误会停止插入,如果设置为 false,MongoDB 会尽可能插入所有文档,即使遇到错误也不会中止操作
- writeConcern: 写关注级别,定义写操作的确认方式
使用技巧:
- 使用 ordered: false 可以提高批量插入的效率,尤其是当插入的数据量较大时
- 在插入之前使用 validate() 方法可对文档进行验证
bulkWrite
bulkWrite 方法允许对多个操作进行批量处理,包括插入、更新和删除
db.collection.bulkWrite(operations, options)
operations: 包含多个操作的数组,每个操作可以是 insertOne, updateOne, deleteOne, replaceOne 等
options: 可选的配置选项,例如 ordered 和 writeConcern
示例:
db.users.bulkWrite([
{
insertOne: {
document: {
name: "Dave", age: 40, email: "[email protected]" } } },
{
insertOne: {
document: {
name: "Eva", age: 28, email: "[email protected]" } } }
]);
参数解析:
operations: 一个包含多个操作的数组,每个操作对象指定了操作类型(insertOne, updateOne, deleteOne, replaceOne)及其参数
options:
- ordered: 布尔值,指定操作的顺序处理方式,默认 true
- writeConcern: 写关注级别,定义写操作的确认方式
使用技巧:
- 在执行大规模的批量操作时,使用 bulkWrite 可以显著提高效率
- 使用 ordered: false 可以避免某些操作失败导致整个批处理失败的情况
replaceOne(仅限于替换操作)
虽然 replaceOne 方法不属于插入操作,但可以结合 upsert 选项来实现插入新文档的功能
db.collection.replaceOne(filter, replacement, options)
filter: 查询条件,用于找到要替换的文档
replacement: 替换文档的新内容
options: 可选的配置选项,例如 upsert 和 writeConcern
示例:
db.users.replaceOne(
{
name: "Alice" },
{
name: "Alice", age: 31, email: "[email protected]" },
{
upsert: true }
);
参数解析:
filter: 用于匹配文档的条件
replacement: 新的文档内容,如果文档存在,它会被完全替换;如果文档不存在且 upsert 为 true,将会插入这个新文档
options:
- upsert: 布尔值,表示如果没有匹配的文档是否插入新文档,默认值为 false
使用技巧:
- 使用 upsert: true 可以在文档不存在时自动插入新文档,但要注意它会完全替换匹配的文档
删
deleteOne
deleteOne 方法用于删除集合中匹配查询条件的第一个文档
db.collection.deleteOne(
filter, // 查询条件
options // 可选参数
)
filter: 查询条件,指定要删除的文档
options: 可选参数,如 collation(用于指定排序规则)
示例:
// 删除名字为 "Alice" 的用户
db.users.deleteOne({
name: "Alice" })
使用技巧:
- deleteOne 只删除匹配条件的第一个文档,如果有多个文档匹配条件,只会删除第一个
deleteMany
deleteMany 方法用于删除集合中所有匹配查询条件的文档
db.collection.deleteMany(
filter, // 查询条件
options // 可选参数
)
示例:
// 删除所有年龄小于 25 的用户
db.users.deleteMany({
age: {
$lt: 25 } })
使用技巧:
- deleteMany 可以批量删除符合条件的文档,在执行删除操作之前,建议使用 find 方法检查匹配的文档,以避免意外删除重要数据
findOneAndDelete
findOneAndDelete 方法用于查找符合查询条件的文档并将其删除,然后返回被删除的文档
db.collection.findOneAndDelete(
filter, // 查询条件
options // 可选参数
)
filter: 查询条件,指定要删除的文档
options: 可选参数,例如 collation(用于指定排序规则)
示例:
// 查找并删除名字为 "Bob" 的用户,返回被删除的文档
db.users.findOneAndDelete({
name: "Bob" })
使用技巧:
- findOneAndDelete 在删除文档的同时返回文档,适用于需要在删除时获取文档内容的场景
drop
drop 方法用于删除整个集合,这个操作会永久删除集合中的所有文档及其索引
db.collection.drop()
示例:
// 删除整个 "users" 集合
db.users.drop()
使用技巧:
- drop 操作是不可恢复的,删除整个集合会导致所有数据丢失,使用前需确保备份数据或确认不再需要这些数据
dropDatabase
dropDatabase 方法用于删除当前数据库,这个操作会删除数据库中的所有集合及其数据
db.dropDatabase()
示例:
// 删除当前数据库
db.dropDatabase()
使用技巧:
- dropDatabase 操作也不可恢复,执行前请确保确认需要删除整个数据库,并且已经备份重要数据
删除操作符
MongoDB 删除操作的实现并不涉及更新操作符,但在删除操作中,有时需要结合查询操作符来准确指定需要删除的文档,例如:
$lt: 查找小于某个值的文档
db.users.deleteMany({
age: {
$lt: 30 } })
$gt: 查找大于某个值的文档
db.users.deleteMany({
age: {
$gt: 40 } })
$in: 查找字段值在指定数组中的文档
db.users.deleteMany({
name: {
$in: ["Alice", "Bob"] } })
$exists: 查找字段存在的文档
db.users.deleteMany({
address: {
$exists: true } })
查
find
find 方法用于检索集合中的文档,可接受查询条件和投影参数
db.collection.find(query, projection)
query: 查询条件,用于筛选文档
projection: 投影参数,用于指定返回的字段
示例:
// 检索所有年龄大于 25 的用户
db.users.find({
age: {
$gt: 25 } })
// 检索名字为 "Alice" 的用户,并只返回 name 和 age 字段
db.users.find({
name: "Alice" }, {
name: 1, age: 1 })
使用技巧:
- 利用索引可以显著提高查询性能,确保经常用于查询条件的字段被索引
- 使用投影参数可以减少网络传输的数据量,只返回需要的字段
findOne
findOne 方法用于检索集合中匹配查询条件的第一个文档
db.collection.findOne(query, projection)
示例:
// 检索名字为 "Bob" 的第一个用户
db.users.findOne({
name: "Bob" })
countDocuments
countDocuments 方法用于计算匹配查询条件的文档数量
db.collection.countDocuments(query, options)
options: 可选参数,包括 limit、skip、hint 等
示例:
// 计算所有年龄大于 30 的用户数量
db.users.countDocuments({
age: {
$gt: 30 } })
distinct
distinct 方法用于返回指定字段的所有不重复值
db.collection.distinct(fieldName, query, options)
fieldName: 要返回不重复值的字段名
query: 可选的查询条件
示例:
// 返回所有不同的年龄值
db.users.distinct("age")
比较操作符
MongoDB 支持多种比较操作符,如 e q (等于)、 eq(等于)、 eq(等于)、gt(大于)、 g t e (大于等于)、 gte(大于等于)、 gte(大于等于)、lt(小于)、 l t e (小于等于)、 lte(小于等于)、 lte(小于等于)、ne(不等于)、 i n (在指定数组中)、 in(在指定数组中)、 in(在指定数组中)、nin(不在指定数组中)等
示例:
// 检索年龄在 20 到 30 之间的用户
db.users.find({
age: {
$gte: 20, $lte: 30 } })
// 检索名字不是 "Alice" 的用户
db.users.find({
name: {
$ne: "Alice" } })
逻辑操作符
MongoDB 支持逻辑操作符,如 a n d 、 and、 and、or 和 $not,用于组合多个查询条件
示例:
// 检索年龄大于 25 且名字为 "Bob" 或 "Alice" 的用户
db.users.find({
$and: [{
age: {
$gt: 25 } }, {
name: {
$in: ["Bob", "Alice"] } }] })
// 检索名字不是 "Bob" 且年龄不大于 30 的用户
db.users.find({
$and: [{
name: {
$ne: "Bob" } }, {
age: {
$not: {
$gt: 30 } } }] })
元素操作符
对于数组字段,MongoDB 提供了元素操作符,如 e x i s t s (检查字段是否存在)、 exists(检查字段是否存在)、 exists(检查字段是否存在)、type(检查字段类型)等
示例:
// 检索拥有 email 字段的用户
db.users.find({
email: {
$exists: true } })
正则表达式
MongoDB 支持使用正则表达式进行模式匹配查询
示例1:
// 检索名字以 "A" 开头的用户
db.users.find({
name: {
$regex: "^A" } }):
示例2:
db.collection.find({
field: {
$regex: /pattern/, $options: 'i' } })
pattern: 正则表达式模式
$options: 可选标志,例如 ‘i’ 表示不区分大小写
// 检索名字中包含 "smith" 的用户,不区分大小写
db.users.find({
name: {
$regex: "smith", $options: 'i' } })
数组操作符
MongoDB 提供了用于查询数组字段的操作符,如 e l e m M a t c h 、 elemMatch、 elemMatch、size 等
$elemMatch: 用于匹配数组中符合多个条件的元素
db.collection.find({
arrayField: {
$elemMatch: {
condition } } })
示例:
// 查找数组中包含 age 大于 25 的用户
db.users.find({
scores: {
$elemMatch: {
age: {
$gt: 25 } } } })
$size: 用于匹配数组的大小,
db.collection.find({
arrayField: {
$size: n } })
示例:
// 查找数组长度为 3 的用户
db.users.find({
scores: {
$size: 3 } })
地理空间查询
MongoDB 提供了地理空间查询功能,支持使用 2d、2dsphere 索引进行地理位置查询
$near: 查找距离指定点最近的文档
db.collection.find({
location: {
$near: {
$geometry: {
type: "Point", coordinates: [longitude, latitude] }, $maxDistance: distance } } })
示例:
// 查找距离指定点 (40.7128, -74.0060) 最近的文档,距离不超过 5 公里
db.places.find({
location: {
$near: {
$geometry: {
type: "Point",
coordinates: [-74.0060, 40.7128]
},
$maxDistance: 5000
}
}
})
$geoWithin: 查找位于指定地理区域内的文档
db.collection.find({
location: {
$geoWithin: {
$geometry: {
type: "Polygon", coordinates: [ [ [lng, lat], ... ] ] } } })
示例:
// 查找位于指定多边形区域内的文档
db.places.find({
location: {
$geoWithin: {
$geometry: {
type: "Polygon",
coordinates: [[[-74.007, 40.712], [-74.005, 40.710], [-74.003, 40.712], [-74.007, 40.712]]]
}
}
}
})
聚合查询
MongoDB 的聚合框架允许用户执行复杂的数据处理和分析操作
$match: 用于过滤数据,类似于 find 方法的查询条件
db.collection.aggregate([
{
$match: {
condition } }
])
示例:
// 聚合查询中过滤年龄大于 25 的用户
db.users.aggregate([
{
$match: {
age: {
$gt: 25 } } }
])
$group: 用于将文档分组,并对分组结果进行汇总
db.collection.aggregate([
{
$group: {
_id: "$field", total: {
$sum: "$anotherField" } } }
])
示例:
// 按年龄分组并计算每组的用户总数
db.users.aggregate([
{
$group: {
_id: "$age", totalUsers: {
$sum: 1 } } }
])
$sort: 用于对聚合结果进行排序
db.collection.aggregate([
{
$sort: {
field: 1 } }
])
示例:
// 按年龄升序排序
db.users.aggregate([
{
$sort: {
age: 1 } }
])
$project: 用于控制输出文档的字段
db.collection.aggregate([
{
$project: {
field1: 1, field2: 1 } }
])
示例:
// 只返回名字和年龄字段
db.users.aggregate([
{
$project: {
name: 1, age: 1 } }
])
游标操作
MongoDB 的查询操作会返回游标对象,允许你对结果进行分页和迭代
skip: 跳过指定数量的文档,用于分页
db.collection.find().skip(n)
示例:
// 跳过前 10 个文档,返回接下来的 5 个文档
db.users.find().skip(10).limit(5)
limit: 限制返回的文档数量
db.collection.find().limit(n)
示例:
// 只返回前 5 个文档
db.users.find().limit(5)
改
updateOne
updateOne 方法用于更新集合中匹配查询条件的第一个文档
db.collection.updateOne(
filter, // 查询条件
update, // 更新操作
options // 可选参数
)
filter: 查询条件,指定需要更新的文档
update: 更新操作,指定更新的内容和操作符
options: 可选参数,例如 upsert(是否插入新文档)
示例:
// 更新名字为 "Alice" 的用户的年龄为 30
db.users.updateOne(
{
name: "Alice" },
{
$set: {
age: 30 } }
)
使用技巧:
- 使用 $set 操作符可以更新指定字段的值,不会影响其他字段
- 使用 $inc 操作符可以增加字段的值,而不是直接设置
updateMany
updateMany 方法用于更新集合中所有匹配查询条件的文档
db.collection.updateMany(
filter, // 查询条件
update, // 更新操作
options // 可选参数
)
示例:
// 将所有年龄小于 25 的用户的状态更新为 "年轻"
db.users.updateMany(
{
age: {
$lt: 25 } },
{
$set: {
status: "年轻" } }
)
使用技巧:
- updateMany 可以批量更新文档,注意检查更新条件以确保只更新所需的文档
replaceOne
replaceOne 方法用于替换集合中匹配查询条件的第一个文档,新的文档完全替换旧文档
db.collection.replaceOne(
filter, // 查询条件
replacement, // 新文档
options // 可选参数
)
filter: 查询条件,指定需要替换的文档
replacement: 新文档,完全替换匹配文档
options: 可选参数,例如 upsert(是否插入新文档)
示例:
// 将名字为 "Bob" 的用户文档完全替换为新文档
db.users.replaceOne(
{
name: "Bob" },
{
name: "Bob", age: 28, status: "更新" }
)
使用技巧:
- replaceOne 操作会替换整个文档,使用时需确保新文档包含所有必需的字段
findOneAndUpdate
findOneAndUpdate 用于查找符合查询条件的文档,并对其进行更新,然后返回更新后的文档或更新前的文档
db.collection.findOneAndUpdate(
filter, // 查询条件
update, // 更新操作
options // 可选参数
)
filter: 查询条件,指定需要更新的文档
update: 更新操作,指定更新的内容和操作符
options: 可选参数,例如 returnOriginal(是否返回更新前的文档)
示例:
// 更新名字为 "Charlie" 的用户的状态为 "活跃",并返回更新后的文档
db.users.findOneAndUpdate(
{
name: "Charlie" },
{
$set: {
status: "活跃" } },
{
returnOriginal: false }
)
使用技巧:
- findOneAndUpdate 允许在更新操作的同时获取文档,可以用于在更新时获取相关数据
findOneAndReplace
findOneAndReplace 用于查找符合查询条件的文档,并用新文档替换,然后返回更新后的文档或更新前的文档
db.collection.findOneAndReplace(
filter, // 查询条件
replacement, // 新文档
options // 可选参数
)
示例:
// 查找名字为 "David" 的用户,并用新文档替换,返回更新后的文档
db.users.findOneAndReplace(
{
name: "David" },
{
name: "David", age: 35, status: "新状态" },
{
returnOriginal: false }
)
使用技巧:
- findOneAndReplace 可以用于在替换文档时获取详细数据,有助于跟踪和验证替换操作
findOneAndDelete
findOneAndDelete 方法用于查找符合查询条件的文档,并将其删除,然后返回被删除的文档
db.collection.findOneAndDelete(
filter, // 查询条件
options // 可选参数
)
示例:
// 查找并删除名字为 "Eve" 的用户,返回被删除的文档
db.users.findOneAndDelete(
{
name: "Eve" }
)
使用技巧:
- findOneAndDelete 适用于需要在删除文档时获取被删除数据的场景
更新操作符
MongoDB 提供了多种更新操作符,以便执行不同类型的更新操作:
$set: 设置字段的值
db.users.updateOne({
name: "Alice" }, {
$set: {
age: 30 } })
$unset: 删除字段
db.users.updateOne({
name: "Alice" }, {
$unset: {
address: "" } })
$inc: 增加字段的值
db.users.updateOne({
name: "Alice" }, {
$inc: {
age: 1 } })
$push: 将元素添加到数组字段
db.users.updateOne({
name: "Alice" }, {
$push: {
scores: 90 } })
$pull: 从数组字段中删除匹配的元素
db.users.updateOne({
name: "Alice" }, {
$pull: {
scores: 90 } })
$addToSet: 将元素添加到数组字段中,但不会添加重复的元素
db.users.updateOne({
name: "Alice" }, {
$addToSet: {
scores: 90 } })
$rename: 重命名字段
db.users.updateOne({
name: "Alice" }, {
$rename: {
oldField: "newField" } })
索引
单字段索引(Single Field Index)
单字段索引是最基本的索引类型,创建在集合的一个字段上
db.collection.createIndex(
{
field: 1 } // 创建升序索引,降序索引使用 -1
)
示例:
// 在 "age" 字段上创建升序索引
db.users.createIndex({
age: 1 })
使用技巧:
- 对于单字段查询条件,单字段索引通常能够提供良好的性能
复合索引(Compound Index)
复合索引是基于多个字段的索引,这种索引可以提高对多个字段的查询效率
db.collection.createIndex(
{
field1: 1, field2: -1 } // 字段1按升序索引,字段2按降序索引
)
示例:
// 在 "age" 和 "name" 字段上创建复合索引
db.users.createIndex({
age: 1, name: -1 })
使用技巧:
- 复合索引的字段顺序很重要,MongoDB 会利用索引中前缀的字段来加速查询
- 复合索引可以加速多字段查询,但请确保字段顺序与查询条件一致
唯一索引(Unique Index)
唯一索引保证索引字段中的所有值都是唯一的,适用于需要唯一约束的场景
db.collection.createIndex(
{
field: 1 }, // 升序索引
{
unique: true } // 唯一索引选项
)
示例:
// 在 "email" 字段上创建唯一索引,确保每个文档的 email 唯一
db.users.createIndex({
email: 1 }, {
unique: true })
使用技巧:
- 唯一索引确保字段值唯一,适用于用户名、邮箱等唯一标识符的字段
- 创建唯一索引时,如果集合中已有重复数据,创建索引会失败
稀疏索引(Sparse Index)
稀疏索引只为那些包含索引字段的文档创建索引,不为字段缺失的文档创建索引
db.collection.createIndex(
{
field: 1 }, // 升序索引
{
sparse: true } // 稀疏索引选项
)
示例:
// 在 "address" 字段上创建稀疏索引
db.users.createIndex({
address: 1 }, {
sparse: true })
使用技巧:
- 稀疏索引适用于字段不是每个文档都有的情况
- 可以节省存储空间和提高查询效率,但不适用于所有情况,特别是当你需要索引所有文档时
部分索引(Partial Index)
部分索引允许创建索引仅覆盖符合特定条件的文档
db.collection.createIndex(
{
field: 1 }, // 升序索引
{
partialFilterExpression: {
condition } } // 部分索引条件
)
示例:
// 创建一个部分索引,只为 "age" 大于 30 的文档创建索引
db.users.createIndex(
{
age: 1 },
{
partialFilterExpression: {
age: {
$gt: 30 } } }
)
使用技巧:
- 部分索引可以提高对特定子集文档的查询效率
- 在设计部分索引时,要确保查询条件与部分索引条件一致
地理空间索引(Geospatial Index)
地理空间索引用于支持地理位置相关的查询,如距离计算、位置查询等
db.collection.createIndex(
{
location: "2dsphere" } // 创建 2dsphere 索引
)
示例:
// 在 "location" 字段上创建 2dsphere 索引,用于支持地理位置查询
db.places.createIndex({
location: "2dsphere" })
使用技巧:
- 2dsphere 索引支持地球坐标的查询,如距离计算和位置查询
- 创建地理空间索引时,请确保文档中的地理坐标格式符合要求
文本索引(Text Index)
文本索引用于全文搜索,支持对文本字段的搜索操作
db.collection.createIndex(
{
field: "text" } // 创建文本索引
)
示例:
// 在 "description" 字段上创建文本索引
db.articles.createIndex({
description: "text" })
使用技巧:
- 文本索引支持对字段中的文本进行全文搜索,适用于需要查找关键词的场景
- 文本索引支持 $text 查询操作,如 $text 查找和 $text.score 排序
哈希索引(Hashed Index)
哈希索引用于支持对字段值的哈希散列,适用于分片键的索引
db.collection.createIndex(
{
field: "hashed" } // 创建哈希索引
)
示例:
// 在 "user_id" 字段上创建哈希索引
db.orders.createIndex({
user_id: "hashed" })
使用技巧:
- 哈希索引用于确保分片键的均匀分布,适用于分片数据集
- 不支持范围查询,适用于精确匹配查询
聚合函数
$match
$match 阶段用于筛选文档,类似于 SQL 中的 WHERE 子句,它根据指定的条件过滤文档
语法:
{
$match: {
<query> } }
示例:
// 查找年龄大于 25 的用户
db.users.aggregate([
{
$match: {
age: {
$gt: 25 } } }
])
使用技巧:
- $match 通常是聚合管道中的第一个阶段,以尽早过滤掉不相关的文档,减少后续操作的计算量
$group
$group 阶段用于将文档分组,并对每个组进行聚合操作,如计算总和、平均值,类似于 SQL 中的 GROUP BY 子句
{
$group: {
_id: <expression>, // 分组字段或表达式
<field1>: {
<accumulator1> : <expression> }, // 聚合操作
<field2>: {
<accumulator2> : <expression> }
} }
示例:
// 计算每个年龄段的用户数量
db.users.aggregate([
{
$group: {
_id: "$age", // 按年龄分组
count: {
$sum: 1 } // 统计每个年龄的用户数量
} }
])
使用技巧:
- _id 字段用于指定分组字段,可以是字段名,也可以是计算表达式
- 常用的累加器有 s u m 、 sum、 sum、avg、 m a x 、 max、 max、min、 f i r s t 、 first、 first、last 等
$sort
$sort 阶段用于对文档进行排序,类似于 SQL 中的 ORDER BY 子句
{
$sort: {
<field1>: <sortOrder1>, <field2>: <sortOrder2> } }
sortOrder 1(升序)或 -1(降序)
示例:
// 按年龄降序排序用户
db.users.aggregate([
{
$sort: {
age: -1 } }
])
使用技巧:
- 在排序之前使用 $match 阶段过滤数据,以减少需要排序的数据量
- 多字段排序时可以指定多个排序条件
$project
$project 阶段用于指定要包含或排除的字段,类似于 SQL 中的 SELECT 子句
{
$project: {
<field1>: <include|exclude>, <field2>: <include|exclude> } }
include: 1 或 true(包含字段)
exclude: 0 或 false(排除字段)
示例:
// 仅保留用户名和年龄字段
db.users.aggregate([
{
$project: {
username: 1, age: 1 } }
])
使用技巧:
- $project 可以用于重命名字段、计算新字段以及控制输出字段
$limit
$limit 阶段用于限制文档的数量,类似于 SQL 中的 LIMIT 子句
{
$limit: <number> }
示例:
// 限制结果为前 10 个文档
db.users.aggregate([
{
$limit: 10 }
])
使用技巧:
- 通常在排序之后使用 $limit 以获取前 N 条记录
- 适用于需要分页显示数据的场景
$skip
$skip 阶段用于跳过指定数量的文档,类似于 SQL 中的 OFFSET 子句
{
$skip: <number> }
示例:
// 跳过前 5 个文档,返回后续文档
db.users.aggregate([
{
$skip: 5 }
])
使用技巧:
- 通常与 $limit 一起使用,用于分页显示数据
- 在大数据集上使用 $skip 可能会影响性能
$unwind
$unwind 阶段用于将数组字段中的每个元素拆分为单独的文档,类似于 SQL 中的 JOIN 操作
{
$unwind: <path> }
path: 要拆分的数组字段路径(以 $ 开头)
示例:
// 将 "items" 数组字段中的每个元素拆分为单独的文档
db.orders.aggregate([
{
$unwind: "$items" }
])
使用技巧:
- $unwind 可以与 g r o u p 、 group、 group、sort 等操作结合使用,以对数组元素进行聚合和排序
- 使用 $unwind 时,请确保数组字段存在于文档中
$lookup
$lookup 阶段用于执行左连接操作,将来自其他集合的数据合并到当前集合中
{
$lookup: {
from: <collection>, // 要连接的集合
localField: <field>, // 当前集合中的字段
foreignField: <field>, // 目标集合中的字段
as: <arrayField> // 合并结果存储的字段名
} }
示例:
// 从 "products" 集合中查找与 "orders" 集合中的 "product_id" 匹配的文档
db.orders.aggregate([
{
$lookup: {
from: "products",
localField: "product_id",
foreignField: "product_id",
as: "product_details"
} }
])
使用技巧:
- $lookup 可以用于实现复杂的连接操作,但性能可能受到影响,特别是对大集合的操作
- 如果需要更高效的连接,可以考虑使用 MongoDB Atlas Data Lake 或 MongoDB Stitch 等工具
$addFields
$addFields 阶段用于添加新字段或更新现有字段的值
{
$addFields: {
<field>: <expression> } }
// 为每个用户添加 "full_name" 字段,值为 "first_name" 和 "last_name" 的组合
db.users.aggregate([
{
$addFields: {
full_name: {
$concat: ["$first_name", " ", "$last_name"] } } }
])
使用技巧:
- $addFields 可以用来计算新字段,或修改现有字段的值
- 与 p r o j e c t 不同, project 不同, project不同,addFields 可以保留原有字段,而不仅仅是控制输出字段
$replaceRoot
$replaceRoot 阶段用于替换文档的根元素,可以将子文档提升为根文档
{
$replaceRoot: {
newRoot: <expression> } }
示例:
// 将 "product_details" 子文档替换为根文档
db.orders.aggregate([
{
$lookup: {
from: "products",
localField: "product_id",
foreignField: "product_id",
as: "product_details"
} },
{
$unwind: "$product_details" },
{
$replaceRoot: {
newRoot: "$product_details" } }
])
使用技巧:
- $replaceRoot 用于将子文档提升为根文档,以简化文档结构
- 在复杂的聚合管道中,可以帮助优化文档结构
$merge
$merge 阶段用于将聚合结果写入新集合或现有集合中
{
$merge: {
into: <collection>, whenMatched: <strategy>, whenNotMatched: <strategy> } }
into: 目标集合名称
whenMatched: 匹配文档的处理策略(如 merge、replace、keepExisting)
whenNotMatched: 没有匹配文档的处理策略(如 insert)
示例:
// 将聚合结果写入 "summary" 集合
db.orders.aggregate([
{
$group: {
_id: "$product_id", total_sales: {
$sum: "$quantity" } } },
{
$merge: {
into: "summary" } }
])
使用技巧:
- $merge 用于将聚合结果持久化到数据库集合中
- 可以选择匹配和不匹配文档的处理策略,以控制如何将数据写入目标集合
$count
$count 阶段用于计算文档的数量,并将结果作为单独的字段返回
{
$count: <countField> }
countField: 用于返回文档数量的字段名称
示例:
// 计算符合条件的用户数量
db.users.aggregate([
{
$match: {
age: {
$gt: 25 } } },
{
$count: "total_users" }
])
使用技巧:
- $count 简化了计算总数的操作,适用于需要获取总数量的场景
- 在大数据集上使用 $count 时,可以先使用 $match 过滤数据,以提高性能
聚合管道优化
尽早使用$match
将 $match 阶段尽早放在聚合管道中,以减少处理的数据量,可显著提高后续阶段的性能
示例:
// 优化前:不先过滤数据
db.orders.aggregate([
{
$group: {
_id: "$product_id", total_sales: {
$sum: "$quantity" } } },
{
$sort: {
total_sales: -1 } }
])
// 优化后:先过滤数据,再进行聚合
db.orders.aggregate([
{
$match: {
order_date: {
$gte: new Date('2024-01-01') } } },
{
$group: {
_id: "$product_id", total_sales: {
$sum: "$quantity" } } },
{
$sort: {
total_sales: -1 } }
])
技巧:
- 将 $match 放在聚合管道的开始位置,确保过滤条件能够尽早减少数据集的大小
使用$project控制字段
在聚合管道中使用 $project 仅保留必要的字段,减少数据传输和处理的开销
示例:
// 优化前:不控制输出字段
db.orders.aggregate([
{
$match: {
status: "shipped" } },
{
$group: {
_id: "$product_id", total_quantity: {
$sum: "$quantity" } } },
{
$sort: {
total_quantity: -1 } }
])
// 优化后:仅保留需要的字段
db.orders.aggregate([
{
$match: {
status: "shipped" } },
{
$project: {
product_id: 1, quantity: 1 } },
{
$group: {
_id: "$product_id", total_quantity: {
$sum: "$quantity" } } },
{
$sort: {
total_quantity: -1 } }
])
技巧:
- 使用 $project 可以减少数据在管道中传递的大小,从而提高性能
使用 l i m i t 和 limit和 limit和skip分页
通过 $limit 和 $skip 实现分页,以减少对大量数据的处理
示例:
// 获取前 10 条记录
db.orders.aggregate([
{
$sort: {
order_date: -1 } },
{
$limit: 10 }
])
// 分页示例:获取第 2 页的数据,每页 10 条记录
db.orders.aggregate([
{
$sort: {
order_date: -1 } },
{
$skip: 10 }, // 跳过第一页数据
{
$limit: 10 } // 获取第二页数据
])
技巧:
- 使用 $limit 和 $skip 进行分页,避免在没有索引的字段上进行大量的数据扫描
- $skip 在大数据集上可能会影响性能,考虑使用更高效的分页策略,例如基于游标的分页
索引优化
确保在聚合管道中使用的字段上创建适当的索引,尤其是在 $match 和 $sort 阶段涉及的字段
示例:
// 创建索引以优化查询
db.orders.createIndex({
order_date: -1 })
技巧:
- 在 $match 和 $sort 阶段涉及的字段上创建索引,能够显著提高性能
- 使用 explain() 方法检查聚合查询的性能,了解索引的使用情况
优化$lookup使用
$lookup 操作可能会导致性能问题,特别是在处理大型集合时,使用 $lookup 时考虑以下优化策略:
- 使用 $lookup的pipeline参数:仅在需要的字段上执行连接,并进行必要的过滤操作
示例:
// 优化前:直接连接
db.orders.aggregate([
{
$lookup: {
from: "products",
localField: "product_id",
foreignField: "product_id",
as: "product_details"
} }
])
// 优化后:使用 pipeline 进行优化
db.orders.aggregate([
{
$lookup: {
from: "products",
let: {
product_id: "$product_id" },
pipeline: [
{
$match: {
$expr: {
$eq: ["$product_id", "$$product_id"] } } },
{
$project: {
product_name: 1, price: 1 } }
],
as: "product_details"
} }
])
技巧:
- 使用 $lookup 的 pipeline 参数可以减少合并的数据量,仅保留所需字段
- 确保连接字段上有索引,以提高连接性能
避免过度使用$unwind
$unwind 阶段会将数组字段拆分为多个文档,这可能导致大量的中间结果,尽量减少不必要的 $unwind 使用
示例:
// 优化前:拆分数组字段后进行聚合
db.orders.aggregate([
{
$unwind: "$items" },
{
$group: {
_id: "$items.product_id", total_quantity: {
$sum: "$items.quantity" } } }
])
// 优化后:避免不必要的拆分
db.orders.aggregate([
{
$project: {
items: 1 } },
{
$group: {
_id: "$_id", total_quantity: {
$sum: "$items.quantity" } } }
])
技巧:
- 避免在不必要的情况下使用 $unwind,如果仅需要处理数组的部分数据,考虑其他优化方式
- 在大数组上使用 $unwind 可能会导致性能问题,尤其是在没有索引的情况下
管道顺序优化
合理安排聚合管道中的阶段顺序,以确保高效的数据处理
示例:
// 优化前:顺序不合理
db.orders.aggregate([
{
$group: {
_id: "$product_id", total_sales: {
$sum: "$quantity" } } },
{
$match: {
total_sales: {
$gt: 100 } } },
{
$sort: {
total_sales: -1 } }
])
// 优化后:合理排序
db.orders.aggregate([
{
$match: {
order_date: {
$gte: new Date('2024-01-01') } } },
{
$group: {
_id: "$product_id", total_sales: {
$sum: "$quantity" } } },
{
$match: {
total_sales: {
$gt: 100 } } },
{
$sort: {
total_sales: -1 } }
])
技巧:
- 将 $match 和 $sort 放在聚合管道的合适位置,减少不必要的计算
- 按照操作逻辑的顺序安排管道阶段,以提高整体性能
使用$facet分析多视角数据
$facet 阶段可以在一个管道中并行处理多个聚合操作,减少多个管道查询的开销
示例:
db.orders.aggregate([
{
$facet: {
totalSales: [
{
$group: {
_id: "$product_id", total_quantity: {
$sum: "$quantity" } } }
],
orderCount: [
{
$count: "total_orders" }
]
} }
])
技巧:
- 使用 $facet 进行多视角的数据分析,可以减少多个单独聚合查询的开销
- 注意 $facet 阶段的内存使用情况,确保足够的资源来处理并行操作
监控和调优
使用 MongoDB 的性能监控工具,如 explain() 方法,来分析和调优聚合查询
示例:
// 使用 explain() 方法检查聚合查询的性能
db.orders.aggregate([
{
$match: {
status: "shipped" } },
{
$group: {
_id: "$product_id", total_quantity: {
$sum: "$quantity" } } }
]).explain("executionStats")
技巧:
- 通过 explain() 方法分析查询的执行计划和性能瓶颈
- 根据 explain() 的输出信息优化索引和管道顺序
主从复制
MongoDB 的主从复制(Replication)是一种数据冗余和高可用性的机制,可保证数据的备份和恢复能力,、,主从复制允许在一个 MongoDB 集群中设置主节点(Primary)和从节点(Secondary),从节点会自动从主节点同步数据
主从复制概述
MongoDB 的主从复制基于副本集(Replica Set)架构,副本集是 MongoDB 提供的一种容错机制,由一个主节点和多个从节点组成,副本集通过将数据从主节点复制到从节点来实现数据冗余,副本集确保在主节点出现故障时,系统能够继续正常运行
主要概念
主节点(Primary): 处理所有的读写请求,并将数据写入到其数据集,只有主节点可以进行写操作
从节点(Secondary): 从主节点同步数据,主要用于处理读请求和数据备份,在主节点故障时,从节点可以被选举为新的主节点
仲裁节点(Arbiter): 不存储数据,只参与选举过程,以确保副本集的选举流程顺利进行,仲裁节点可以帮助确定哪个节点成为新的主节点,但不参与数据同步
设置副本集
1. 基本步骤
1. 启动 MongoDB 实例
启动多个 MongoDB 实例,确保它们能够进行网络通信
mongod --replSet "myReplicaSet" --dbpath /data/db1 --port 27017
mongod --replSet "myReplicaSet" --dbpath /data/db2 --port 27018
mongod --replSet "myReplicaSet" --dbpath /data/db3 --port 27019
2. 初始化副本集
连接到主节点,使用 rs.initiate() 初始化副本集
// 连接到主节点
mongo --port 27017
// 初始化副本集
rs.initiate({
_id: "myReplicaSet",
members: [
{
_id: 0, host: "localhost:27017" },
{
_id: 1, host: "localhost:27018" },
{
_id: 2, host: "localhost:27019" }
]
})
3. 检查副本集状态
使用 rs.status() 检查副本集的状态,确认副本集配置正确,节点状态正常
rs.status()
2. 参数解析
_id: 副本集的名称
members: 副本集的成员列表,每个成员需要指定 _id 和 host,_id 是成员的唯一标识符,host 是成员的主机和端口
3. 配置副本集选项
副本集可以通过修改配置来调整其行为
1. 更改成员配置
连接到副本集的主节点,使用 rs.reconfig() 修改副本集配置
var config = rs.conf();
config.members[0].priority = 2; // 提高主节点的优先级
rs.reconfig(config)
2. 设置读写分离
通过读取副本集中的从节点进行读操作,可以提高系统的读性能,可以在应用程序中配置读取偏好
// 连接到副本集
var db = connect("mongodb://localhost:27017,localhost:27018,localhost:27019/mydb");
// 设置读偏好为从节点
db.getMongo().setReadPref("secondaryPreferred");
读偏好选项:
primary: 所有读取操作都发送到主节点
primaryPreferred: 优先读取主节点,如果主节点不可用,则从从节点读取
secondary: 所有读取操作都发送到从节点
secondaryPreferred: 优先从从节点读取,如果从节点不可用,则从主节点读取
nearest: 从最接近的节点读取
3. 配置仲裁节点
仲裁节点不存储数据,只参与副本集的选举过程,可以在副本集中添加仲裁节点来提高选举的稳定性
// 添加仲裁节点
rs.addArb("localhost:27020")
4. 备份和恢复
副本集可以用于备份和恢复数据,可以从从节点中备份数据,主节点的备份会导致性能问题
备份
# 使用 mongodump 进行备份
mongodump --host localhost --port 27019 --out /backup/backup1
恢复
# 使用 mongorestore 恢复数据
mongorestore --host localhost --port 27017 /backup/backup1
5. 故障恢复
副本集自动处理主节点故障,进行选举产生新的主节点,副本集中的从节点会同步数据,保持数据一致性
手动故障恢复
1. 强制重新选举
如果需要手动强制重新选举主节点,可以使用 rs.stepDown() 命令
// 强制当前主节点下线
rs.stepDown()
2. 添加或删除节点
添加或删除副本集成员,使用 rs.add() 和 rs.remove() 命令
// 添加节点
rs.add("localhost:27020")
// 删除节点
rs.remove("localhost:27019")
分片
MongoDB 的分片(Sharding)是一种横向扩展(scaling out)技术,用于处理大规模数据集和高吞吐量的应用场景,通过分片,可以将数据分布到多个节点上,从而提高数据存储能力和查询性能
1. 分片概述
分片: 一个 MongoDB 实例或副本集,存数据的实际副本,数据根据分片键(Shard Key)分布在不同的分片中
分片键: 用于将数据分配到不同分片的字段,选择合适的分片键对分片系统的性能至关重要
配置服务器: 存储分片元数据和路由信息的 MongoDB 实例,分片系统的所有操作都依赖于配置服务器
路由服务: 客户端与分片集群交互的入口,它负责根据分片键将请求路由到相应的分片
2. 设置分片
1. 启动配置服务器
启动配置服务器,通常配置服务器是副本集,假设配置服务器在端口 27019
mongod --configsvr --replSet "configReplSet" --dbpath /data/configdb --port 27019
2. 启动分片
启动多个分片节点,通常每个分片是一个副本集,假设分片节点在端口 27017、27018 和 27020
mongod --shardsvr --replSet "shard1" --dbpath /data/shard1 --port 27017
mongod --shardsvr --replSet "shard2" --dbpath /data/shard2 --port 27018
mongod --shardsvr --replSet "shard3" --dbpath /data/shard3 --port 27020
3. 启动路由服务
启动 mongos 路由服务,mongos 负责将客户端请求路由到正确的分片
mongos --configdb configReplSet/localhost:27019 --port 27017
4. 初始化配置
连接到 mongos 路由服务并初始化分片集群
// 连接到 mongos
mongo --port 27017
// 添加分片
sh.addShard("shard1/localhost:27017")
sh.addShard("shard2/localhost:27018")
sh.addShard("shard3/localhost:27020")
// 添加分片集的配置
sh.enableSharding("mydatabase")
解析:
sh.addShard(): 将分片添加到分片集群
sh.enableSharding(): 启用数据库的分片功能
5. 选择分片键并分片集合
为要分片的集合选择分片键,并启用集合的分片
// 选择分片键并分片集合
sh.shardCollection("mydatabase.mycollection", {
"shardKeyField": 1 })
解析:
- sh.shardCollection():为集合选择分片键,并启用分片,shardKeyField 是用于分片的字段
3. 参数解析
配置服务器
–configsvr: 指定实例作为配置服务器
–replSet “configReplSet”: 配置服务器副本集的名称
–dbpath: 数据存储路径
–port: 配置服务器的端口
分片
–shardsvr: 指定实例作为分片
–replSet “shard1”: 分片副本集的名称
–dbpath: 数据存储路径
–port: 分片的端口
路由服务
–configdb: 配置服务器副本集的连接字符串
–port: mongos 的端口
4. 使用技巧
选择分片键
选择高基数字段: 分片键应该具有高基数,以确保数据均匀分布在所有分片上
避免热点: 选择写操作频繁的字段作为分片键可能会导致某些分片负载过重,选择写操作分布均匀的字段作为分片键
预估数据增长: 选择适合数据增长模式的分片键,以避免后期的重新分片操作
监控和维护
监控分片性能: 使用 MongoDB 的监控工具(如 mongostat 和 mongotop)来监控分片集群的性能和负载
重新平衡数据: 如果某个分片的数据量显著大于其他分片,可以使用 sh.moveChunk() 手动平衡数据
// 手动移动数据块
sh.moveChunk("mydatabase.mycollection", {
"shardKeyField": 10 }, "shard2")
解析:
- sh.moveChunk():将特定的数据块移动到指定的分片
- 定期维护:定期检查分片集群的状态,确保分片的正常运行
备份和恢复
备份: 使用 mongodump 和 mongorestore 进行备份和恢复,确保备份和恢复操作不会影响分片的正常运行
# 备份
mongodump --host localhost --port 27017 --out /backup
# 恢复
mongorestore --host localhost --port 27017 /backup
跨分片备份: 备份时,确保所有分片的数据都被备份,避免数据不一致
5. 故障处理
分片故障: 如果某个分片发生故障,可以将故障分片从集群中移除并修复,然后重新添加
// 移除分片
sh.removeShard("shard3")
// 修复并重新添加
sh.addShard("shard3/localhost:27020")
配置服务器故障: 配置服务器副本集的故障可以通过副本集机制自动恢复
事务
MongoDB 的事务功能允许在多个操作之间实现 ACID(原子性、一致性、隔离性、持久性)特性,确保数据的完整性和一致性,事务在处理涉及多个文档或集合的操作时尤其重要
1. 事务概述
事务: 一组操作,这些操作要么全部成功,要么全部失败,事务确保所有操作都以原子性方式执行,即要么所有操作都生效,要么全部回滚
原子性: 事务中的所有操作要么全部成功,要么全部失败,没有中间状态
一致性: 事务完成后,数据库必须保持一致性状态
隔离性: 事务对其他事务的操作是隔离的,确保事务之间不会相互干扰
持久性: 一旦事务提交,其结果是持久的,即使系统崩溃也能保持
2. 使用事务
MongoDB 支持两种类型的事务:
单文档事务: 单文档事务是一种简单的事务,保证单个文档的原子性操作,MongoDB 中的所有写操作本身都是原子的,因此单文档事务通常不需要显式事务支持
多文档事务: 用于跨多个文档、集合甚至数据库的操作,支持更复杂的操作
多文档事务的使用示例
1. 启动客户端会话
要使用多文档事务,首先需要在客户端创建一个会话(Session)
const session = client.startSession();
2. 开始事务
使用 startTransaction 方法开始事务
session.startTransaction();
3. 执行操作
在事务中执行所需的数据库操作,所有操作必须使用同一个会话对象
try {
const collection1 = client.db('mydatabase').collection('collection1');
const collection2 = client.db('mydatabase').collection('collection2');
// 执行写操作
await collection1.insertOne({
_id: 1, field: 'value' }, {
session });
await collection2.updateOne({
_id: 2 }, {
$set: {
field: 'new value' } }, {
session });
// 提交事务
await session.commitTransaction();
} catch (error) {
// 回滚事务
await session.abortTransaction();
console.error('Transaction aborted due to an error:', error);
} finally {
// 结束会话
session.endSession();
}
解析:
startSession(): 启动一个会话
startTransaction(): 在会话中开始事务
commitTransaction(): 提交事务,将所有操作持久化
abortTransaction(): 回滚事务,撤销所有操作
endSession(): 结束会话
3. 参数解析
readConcern: 定义事务的读取一致性级别,常见的级别有:
- local:默认级别,读取最近的数据
- majority:读取已经被大多数副本集成员确认的数据
- linearizable:保证读取的结果是最新的,但可能会影响性能
writeConcern: 定义事务的写入确认级别,常见的级别有:
- w: 1:仅确认写入到主节点
- w: “majority”:确认写入到大多数节点
session.startTransaction({
readConcern: {
level: 'majority' },
writeConcern: {
w: 'majority' }
});
maxTimeMS: 设置事务的超时时间
session.startTransaction({
maxTimeMS: 60000 // 60 秒
});
4. 使用技巧
选择合适的事务级别
短事务: 尽量缩短事务的持续时间,以减少锁定时间和资源占用,对于长时间运行的事务,系统的性能可能会受到显著影响
批量操作: 将多个操作组合到一个事务中,避免在事务中进行过多的操作,大规模的事务可能导致性能瓶颈和资源竞争
监控事务
事务监控: 使用 MongoDB 的监控工具来观察事务的状态和性能,工具如 mongostat 和 MongoDB Atlas 提供的监控功能可以帮助识别潜在的事务问题
mongostat --port 27017
处理错误和回滚
错误处理: 确保在事务中处理可能出现的错误,使用 try-catch 块来捕获异常并回滚事务
事务超时: 设置合适的 maxTimeMS,以避免长时间的事务锁定系统资源
跨库事务
跨数据库事务: MongoDB 支持跨数据库的事务,但可能会影响性能,确保在设计数据模型时,尽量避免跨数据库事务的需求
// 开启跨数据库事务
session.startTransaction({
readConcern: {
level: 'majority' },
writeConcern: {
w: 'majority' }
});
// 跨数据库操作
await client.db('db1').collection('coll1').insertOne({
a: 1 }, {
session });
await client.db('db2').collection('coll2').insertOne({
b: 2 }, {
session });
// 提交事务
await session.commitTransaction();
5. 故障处理
事务失败: 在处理事务时,确保能够处理事务失败的情况,回滚事务并记录错误信息,便于后续分析
重新尝试: 在遇到事务失败时,考虑实现重试逻辑,以便在出现临时性问题时恢复操作
数据恢复与故障恢复
MongoDB支持日志记录和存储引擎级别的数据恢复,并且当主节点发生故障时,复制集中的其他副本可以快速提升为主节点,保证数据持续可用
1. 数据恢复机制
1.1 备份与恢复
备份是确保数据安全性的重要手段,而在 MongoDB 中,常见的备份和恢复方式有:
逻辑备份: 使用 mongodump 和 mongorestore 工具导出和导入数据
物理备份: 直接复制数据库文件,可以使用文件系统层面的备份工具
逻辑备份示例
备份数据: 使用 mongodump 工具创建逻辑备份
mongodump --db mydatabase --out /path/to/backup
–db: 指定要备份的数据库
–out: 指定备份输出目录
恢复数据: 使用 mongorestore 恢复逻辑备份
mongorestore --db mydatabase /path/to/backup/mydatabase
- –db:指定要恢复到的数据库
- 后面的路径为备份存储的位置
参数解析
参数 | 说明 |
---|---|
–db | 要备份或恢复的数据库名称 |
–out | 备份文件的输出目录 |
–gzip | 支持对备份进行 gzip 压缩 |
–drop | 恢复时删除目标集合,如果集合已存在 |
1.2 使用复制集进行数据恢复
MongoDB 的复制集功能提供了高可用性和数据冗余,可以在主节点失败时自动故障转移到从节点
设置复制集: 在 MongoDB 中配置复制集,提高数据的可用性
rs.initiate();
rs.add("mongodb1:27017");
rs.add("mongodb2:27017");
故障转移: 当主节点宕机时,从节点会自动选举新的主节点
2. 故障恢复机制
故障恢复是指在发生系统故障时,能够有效地恢复到一定的服务状态
2.1 使用 WiredTiger 存储引擎的恢复特性
MongoDB 的 WiredTiger 存储引擎提供了良好的故障恢复能力,支持快速恢复和写操作的持久性
数据提交: WiredTiger 会在每次数据修改时记录操作,这确保了在系统崩溃时不会丢失最近的操作
2.2 进行“轻量级”恢复
当遇到写节点故障时,可以通过从节点的 oplog 进行恢复
查看 oplog: 可以查看 oplog 中的操作记录
use local;
db.oplog.rs.find().limit(10);
手动回滚操作: 在从节点上手动恢复未完成的数据状态
参数解析
参数 | 说明 |
---|---|
use local | 切换到 local 数据库,查看 oplog |
find() | 查询 oplog 中的操作记录 |
3. 恢复策略与使用技巧
3.1 定期备份
安排定期备份: 根据数据更新频率和业务需求,定期使用 mongodump 创建逻辑备份
增量备份: 根据应用需求,结合 mongodump 和 mongorestore 的 --gzip 参数进行增量备份
3.2 监控复制集状态
使用 rs.status(): 监控复制集的状态,确保副本集中的所有节点正常运行
rs.status();
设置告警: 针对节点宕机或数据复制延迟设置告警,确保能够及时处理故障
3.3 故障测试
模拟故障: 定期进行故障模拟测试,以验证系统恢复能力
测试备份恢复过程: 在非生产环境中测试备份和恢复流程,确保在真实的故障发生时能够快速恢复
4. 使用mongod 提供的恢复选项
在启动 mongod 实例时,可以使用一些参数来进行恢复
–repair: 尝试修复 MongoDB 数据文件,但仅在特定情况下使用
mongod --repair --dbpath /path/to/data
优化查询性能
包括使用索引、适当使用$index hint、调整分片策略等,以减少磁盘读写和提高查询效率
1. 索引优化
1.1 创建索引
- 创建单字段索引
db.collection.createIndex({
field: 1 });
- field:要索引的字段名称
- 1:表示升序索引,-1 表示降序索引
创建复合索引
db.collection.createIndex({
field1: 1, field2: -1 });
- field1 和 field2:要索引的字段,索引将按 field1 升序,field2 降序排序
创建唯一索引
db.collection.createIndex({
field: 1 }, {
unique: true });
- unique: true:确保索引字段的值唯一
1.2 使用 explain() 方法
查看查询计划
db.collection.find({
field: value }).explain("executionStats");
- explain(“executionStats”):提供详细的执行统计信息,帮助分析查询性能
参数解析
参数 | 说明 |
---|---|
field | 要创建索引的字段名称 |
1 或 -1 | 索引的排序方式,1 为升序,-1 为降序 |
unique | 如果设置为 true,则索引字段的值必须唯一 |
1.3 移除不必要的索引
查看所有索引
db.collection.getIndexes();
删除索引
db.collection.dropIndex("indexName");
- indexName:要删除的索引名称
2. 查询优化
2.1 使用合适的查询条件
利用索引字段进行查询
db.collection.find({
indexedField: value });
确保查询条件中的字段是索引字段,以充分利用索引加速查询
避免全表扫描
尽量避免不使用索引的查询,例如 find() 查询中没有字段条件的情况
2.2 使用投影来减少返回的数据量
- 只返回需要的字段
db.collection.find({
field: value }, {
projection: {
field1: 1, field2: 1 } });
- projection:指定返回的字段,1 表示包含,0 表示排除
3. 集合和文档设计优化
3.1 规范化与反规范化
规范化: 将重复的数据分离到不同的集合中,以减少数据冗余,例如,将用户信息和订单信息分别存储在两个集合中
反规范化: 将相关数据嵌入到同一个文档中,以减少查询次数,例如,将订单项嵌入到订单文档中
// 嵌套文档示例
{
_id: 1,
orderItems: [
{
itemId: 101, quantity: 2 },
{
itemId: 102, quantity: 1 }
]
}
3.2 使用合理的数据类型
选择合适的数据类型: 选择数据类型时应考虑存储效率和查询性能,例如,使用 int 替代 string 存储整数类型的字段
4. 聚合管道优化
4.1 使用索引优化 $match 阶段
- 在 $match 阶段使用索引字段
db.collection.aggregate([
{
$match: {
indexedField: value } },
{
$group: {
_id: "$groupField", total: {
$sum: "$amount" } } }
]);
确保 $match 阶段使用索引字段,以提高性能
4.2 限制处理的数据量
- 使用 $limit和 $skip 限制结果集
db.collection.aggregate([
{
$match: {
field: value } },
{
$limit: 100 }
]);
- $limit:限制返回的文档数量
- $skip:跳过指定数量的文档,用于分页查询
5. 内存和缓存优化
5.1 调整 WiredTiger 缓存设置
- 调整 storage.wiredTiger.engineConfig.cacheSizeGB
storage:
wiredTiger:
engineConfig:
cacheSizeGB: 2
- cacheSizeGB:设置 WiredTiger 存储引擎的缓存大小
5.2 配置适当的 maxTimeMS 限制
- 设置查询超时
db.collection.find({
field: value }).maxTimeMS(5000);
- maxTimeMS:限制查询的最大执行时间,单位为毫秒
6. 使用 Sharding 提升性能
6.1 配置 Shard Key
- 选择合适的 Shard Key
db.collection.createIndex({
shardKey: 1 });
db.adminCommand({
shardCollection: "mydb.collection", key: {
shardKey: 1 } });
- shardKey:选择用于分片的字段
6.2 监控和调整 Shard 配置
- 查看分片状态
db.adminCommand({
listShards: 1 });
- listShards:列出所有的分片信息