MongoDB 数据库
MongoDB 核心概念、数据模型、聚合查询与最佳实践
MongoDB 概述
特点
- 文档数据库(BSON 格式)
- 模式灵活(Schema-less)
- 支持索引
- 分布式设计
- 丰富的查询语言
应用场景
- 内容管理系统
- 用户数据存储
- 日志存储
- 实时分析
- 移动应用后端
数据模型
文档结构
javascript
// 灵活的文档结构
{
_id: ObjectId("..."),
name: "Alice",
age: 25,
email: "alice@example.com",
tags: ["developer", "frontend"],
address: {
street: "123 Main St",
city: "Beijing",
country: "China"
},
createdAt: new Date(),
updatedAt: new Date()
}
// 数组内嵌文档
{
orders: [
{ orderId: 1, amount: 100, status: "paid" },
{ orderId: 2, amount: 200, status: "shipped" }
]
}模式设计原则
javascript
// 1. 内嵌(Embed)- 一对一关系
// 适合经常一起查询的数据
{
user: {
name: "Alice",
profile: {
bio: "Developer",
avatar: "url",
website: "https://alice.dev"
}
}
}
// 2. 引用(Reference)- 一对多/多对多
// 适合数据量大的场景
// users 集合
{
_id: ObjectId("..."),
name: "Alice",
email: "alice@example.com"
}
// posts 集合(引用 user)
{
_id: ObjectId("..."),
userId: ObjectId("user_id"),
title: "My Post",
content: "..."
}
// 3. 子集模式(Subset)- 只存储常用字段
{
_id: ObjectId("..."),
userId: ObjectId("user_id"),
title: "Post title",
// 只存储前10条评论的摘要
recentComments: [
{ text: "Great!", author: "Bob" }
]
}CRUD 操作
插入
javascript
// 单文档插入
await db.collection('users').insertOne({
name: 'Alice',
age: 25,
createdAt: new Date()
})
// 多文档插入
await db.collection('users').insertMany([
{ name: 'Bob', age: 30 },
{ name: 'Charlie', age: 35 }
])
// 插入或更新(Upsert)
await db.collection('users').updateOne(
{ email: 'alice@example.com' },
{
$set: { name: 'Alice Updated' },
$setOnInsert: { createdAt: new Date() }
},
{ upsert: true }
)查询
javascript
// 基本查询
await db.collection('users').findOne({ name: 'Alice' })
await db.collection('users').find({ age: { $gte: 25 } }).toArray()
// 比较操作符
{
age: { $gt: 25 }, // 大于
age: { $gte: 25 }, // 大于等于
age: { $lt: 30 }, // 小于
age: { $lte: 30 }, // 小于等于
age: { $ne: 25 }, // 不等于
age: { $in: [25, 30, 35] }, // 在数组中
age: { $nin: [20, 40] } // 不在数组中
}
// 逻辑操作符
{
$and: [{ age: { $gte: 25 } }, { age: { $lte: 35 } }],
$or: [{ age: { $lt: 25 } }, { age: { $gt: 35 } }],
$not: { age: { $gt: 30 } }
}
// 元素操作符
{
name: { $exists: true },
age: { $type: 'number' }
}
// 数组操作符
{
tags: { $in: ['developer', 'frontend'] },
tags: { $all: ['developer', 'frontend'] }, // 包含所有
scores: { $elemMatch: { $gte: 90 } } // 至少一个元素满足
}
// 投影(只返回指定字段)
await db.collection('users').find(
{ age: { $gte: 25 } },
{ projection: { name: 1, age: 1, _id: 0 } } // 1-返回,0-不返回
)
// 分页
const page = 1
const pageSize = 10
await db.collection('users')
.find({})
.skip((page - 1) * pageSize)
.limit(pageSize)
.toArray()
// 排序
await db.collection('users')
.find({})
.sort({ age: 1, name: -1 }) // 1-升序,-1-降序
.toArray()
// 统计
const count = await db.collection('users').countDocuments({ age: { $gte: 25 } })更新
javascript
// 更新单个
await db.collection('users').updateOne(
{ name: 'Alice' },
{
$set: { age: 26 }, // 设置字段
$inc: { views: 1 }, // 递增
$push: { tags: 'senior' }, // 数组添加
$pull: { tags: 'junior' }, // 数组删除
$addToSet: { tags: 'expert' } // 数组去重添加
}
)
// 更新多个
await db.collection('users').updateMany(
{ age: { $lt: 30 } },
{ $set: { status: 'young' } }
)
// 数组更新
await db.collection('users').updateOne(
{ name: 'Alice', 'orders.orderId': 1 },
{
$set: { 'orders.$.status': 'delivered' } // $ 定位符
}
)
// 更新嵌套字段
await db.collection('users').updateOne(
{ name: 'Alice' },
{ $set: { 'address.city': 'Shanghai' } }
)
// 更新数组内所有元素
await db.collection('users').updateOne(
{ name: 'Alice' },
{ $inc: { 'scores.$[]': 1 } } // $[] 更新所有元素
)
// 条件数组更新
await db.collection('users').updateOne(
{ name: 'Alice', 'scores': { $gte: 90 } },
{ $set: { 'scores.$[elem]': 100 } },
{ arrayFilters: [{ elem: { $gte: 90 } }] }
)删除
javascript
// 删除单个
await db.collection('users').deleteOne({ name: 'Alice' })
// 删除多个
await db.collection('users').deleteMany({ status: 'inactive' })
// 删除集合
await db.collection('users').drop()聚合框架
管道阶段
javascript
// 聚合管道
const pipeline = [
// 1. 过滤
{ $match: { status: 'active' } },
// 2. 展开数组
{ $unwind: '$orders' },
// 3. 添加计算字段
{
$addFields: {
orderYear: { $year: '$orders.date' }
}
},
// 4. 分组
{
$group: {
_id: '$orderYear',
totalRevenue: { $sum: '$orders.amount' },
orderCount: { $sum: 1 },
avgOrderValue: { $avg: '$orders.amount' }
}
},
// 5. 排序
{ $sort: { totalRevenue: -1 } },
// 6. 限制
{ $limit: 10 },
// 7. 投影
{
$project: {
_id: 0,
year: '$_id',
revenue: '$totalRevenue',
orders: '$orderCount'
}
}
]
const results = await db.collection('users').aggregate(pipeline).toArray()聚合操作符
javascript
// 字符串操作
{
$project: {
fullName: { $concat: ['$firstName', ' ', '$lastName'] },
emailDomain: { $arrayElemAt: [{ $split: ['$email', '@'] }, 1] }
}
}
// 条件判断
{
$project: {
status: {
$switch: {
branches: [
{ case: { $gte: ['$score', 90] }, then: 'A' },
{ case: { $gte: ['$score', 80] }, then: 'B' }
],
default: 'C'
}
}
}
}
// 日期操作
{
$project: {
year: { $year: '$createdAt' },
month: { $month: '$createdAt' },
day: { $dayOfMonth: '$createdAt' },
dayOfWeek: { $dayOfWeek: '$createdAt' }
}
}
// 数组操作
{
$project: {
firstTag: { $arrayElemAt: ['$tags', 0] },
tagCount: { $size: '$tags' },
hasFrontend: { $in: ['frontend', '$tags'] }
}
}索引
javascript
// 单字段索引
await db.collection('users').createIndex({ email: 1 })
// 复合索引
await db.collection('users').createIndex({ status: 1, createdAt: -1 })
// 唯一索引
await db.collection('users').createIndex({ email: 1 }, { unique: true })
// 多键索引(数组字段)
await db.collection('products').createIndex({ tags: 1 })
// 文本索引
await db.collection('articles').createIndex({ title: 'text', content: 'text' })
// 部分索引
await db.collection('users').createIndex(
{ email: 1 },
{ partialFilterExpression: { email: { $exists: true } } }
)
// 过期索引(TTL)
await db.collection('sessions').createIndex(
{ createdAt: 1 },
{ expireAfterSeconds: 3600 }
)
// 查看索引
await db.collection('users').getIndexes()
// 删除索引
await db.collection('users').dropIndex('email_1')事务
javascript
// 4.0+ 单集合事务
const session = db.startSession()
session.startTransaction()
try {
await db.collection('accounts').updateOne(
{ accountId: 'A' },
{ $inc: { balance: -100 } },
{ session }
)
await db.collection('accounts').updateOne(
{ accountId: 'B' },
{ $inc: { balance: 100 } },
{ session }
)
await session.commitTransaction()
} catch (error) {
await session.abortTransaction()
throw error
} finally {
await session.endSession()
}Node.js 集成
mongoose
javascript
import mongoose from 'mongoose'
// 连接
await mongoose.connect('mongodb://localhost:27017/myapp', {
maxPoolSize: 10,
serverSelectionTimeoutMS: 5000
})
// 定义 Schema
const userSchema = new mongoose.Schema({
name: {
type: String,
required: true,
trim: true,
minlength: 2,
maxlength: 50
},
email: {
type: String,
required: true,
unique: true,
lowercase: true
},
age: {
type: Number,
min: 0,
max: 150
},
tags: [String],
status: {
type: String,
enum: ['active', 'inactive', 'pending'],
default: 'pending'
},
createdAt: {
type: Date,
default: Date.now
}
}, {
timestamps: true // 自动管理 createdAt 和 updatedAt
})
// 索引
userSchema.index({ email: 1 })
userSchema.index({ status: 1, createdAt: -1 })
// 虚拟字段
userSchema.virtual('fullInfo').get(function() {
return `${this.name} (${this.email})`
})
// 实例方法
userSchema.methods.greet = function() {
return `Hello, I'm ${this.name}`
}
// 静态方法
userSchema.statics.findByEmail = function(email) {
return this.findOne({ email })
}
// 钩子
userSchema.pre('save', function(next) {
if (this.isModified('email')) {
this.email = this.email.toLowerCase()
}
next()
})
// 模型
export const User = mongoose.model('User', userSchema)
// 使用
const user = new User({ name: 'Alice', email: 'ALICE@EXAMPLE.COM' })
await user.save()
const users = await User.find({ status: 'active' })
.select('name email')
.sort({ createdAt: -1 })
.limit(10)
const user = await User.findByEmail('alice@example.com')原生驱动
javascript
import { MongoClient } from 'mongodb'
const client = new MongoClient('mongodb://localhost:27017', {
maxPoolSize: 10,
minPoolSize: 2
})
await client.connect()
const db = client.db('myapp')
// 集合操作
const users = db.collection('users')
// CRUD
await users.insertOne({ name: 'Alice', age: 25 })
await users.find({ age: { $gte: 25 } }).toArray()
await users.updateOne({ name: 'Alice' }, { $set: { age: 26 } })
await users.deleteOne({ name: 'Alice' })
// 事务
const session = client.startSession()
await session.withTransaction(async () => {
await users.updateOne({ _id: id1 }, { $inc: { balance: -100 } }, { session })
await users.updateOne({ _id: id2 }, { $inc: { balance: 100 } }, { session })
})最佳实践
javascript
// 1. 连接池配置
const client = new MongoClient(uri, {
maxPoolSize: 100,
minPoolSize: 10,
maxIdleTimeMS: 60000,
waitQueueTimeoutMS: 30000
})
// 2. 批量操作
const bulkOps = users.map(user => ({
updateOne: {
filter: { _id: user._id },
update: { $set: user }
}
}))
await users.bulkWrite(bulkOps, { ordered: false })
// 3. 游标处理大结果集
const cursor = users.find({})
for await (const doc of cursor) {
// 处理每个文档
}
// 4. 投影只取需要的字段
await users.find({}, {
projection: { name: 1, email: 1 },
limit: 100
})
// 5. 使用 explain 分析查询
const explain = await users.find({ status: 'active' }).explain('executionStats')
// 6. 软删除
userSchema.add({
deletedAt: Date,
deleted: { type: Boolean, default: false }
})
userSchema.pre('find', function() {
this.where({ deleted: { $ne: true } })
})
// 7. 分片键选择
// 原则:分散写入、查询局部性、避免热点
sh.shardCollection('myapp.orders', { userId: 1, orderDate: 1 })面试高频题
Q1: MongoDB 和 MySQL 的区别?
| 特性 | MongoDB | MySQL |
|---|---|---|
| 类型 | 文档数据库 | 关系数据库 |
| 模式 | 灵活(Schema-less) | 固定 |
| 查询语言 | MongoDB 查询语法 | SQL |
| 事务 | 4.0+ 支持多文档 | 原生支持 |
| 扩展 | 水平扩展(分片) | 垂直扩展/读写分离 |
| 关联 | 引用/聚合 | JOIN |
Q2: MongoDB 索引类型有哪些?
答案:
- 单字段索引
- 复合索引
- 多键索引(数组字段)
- 文本索引
- 哈希索引
- 地理空间索引
- 部分索引
- TTL 索引
Q3: 如何设计 MongoDB 的数据模型?
答案:
- 内嵌:一对一、经常联合查询的数据
- 引用:一对多、数据量大、需要独立更新的数据
- 子集:大集合中只存储常用字段
Q4: 聚合管道和 MapReduce 的区别?
| 特性 | 聚合管道 | MapReduce |
|---|---|---|
| 性能 | 更优 | 较低 |
| 灵活性 | 高 | 更高 |
| 实时性 | 实时 | 批量处理 |
| 使用场景 | 复杂查询 | 超大规模数据 |
Q5: MongoDB 如何实现分片?
答案:
- 配置分片(Config Servers)
- 路由分片(Mongos)
- 数据分片(Shard Servers)
- 选择分片键(Shard Key)
- 数据自动均衡