概述
MongoDB 是一个基于分布式文件存储的 NoSQL 文档数据库,由 C++ 语言编写。具有以下特点:
- 文档存储:使用 BSON 格式存储数据,支持丰富的数据类型
- 动态模式:无需预定义表结构,灵活的数据模型
- 高性能:内存计算引擎,支持丰富的查询操作
- 高可用:副本集提供故障转移和数据冗余
- 水平扩展:分片集群支持海量数据存储
💡 提示: MongoDB 特别适合内容管理系统、移动应用、IoT 数据存储等场景。
环境准备
系统要求
- Docker 20.10+
- 内存:至少 1GB RAM
- 存储:SSD 推荐
- 网络:稳定的网络连接
依赖检查
# 检查 Docker 版本
docker --version
# 检查可用内存
free -h
# 检查磁盘空间
df -h
Docker 安装部署
1. 查看可用的 MongoDB 版本
访问 MongoDB 镜像库地址:https://hub.docker.com/_/mongo
也可以用命令来查看:
docker search mongo
2. 拉取指定版本的 MongoDB 镜像
# 拉取最新版本
docker pull mongo:latest
# 或者拉取指定版本
docker pull mongo:7.0
3. 查看本地镜像
docker images | grep mongo
4. 单节点部署
# 基本部署
docker run -d \
--name mongodb \
-p 27017:27017 \
-v mongodb_data:/data/db \
mongo:latest
# 带认证的部署
docker run -d \
--name mongodb \
-p 27017:27017 \
-v mongodb_data:/data/db \
-e MONGO_INITDB_ROOT_USERNAME=admin \
-e MONGO_INITDB_ROOT_PASSWORD=password123 \
mongo:latest
5. 使用 Docker Compose 部署
创建 docker-compose.yml 文件:
version: '3.8'
services:
mongodb:
image: mongo:latest
container_name: mongodb
restart: unless-stopped
environment:
MONGO_INITDB_ROOT_USERNAME: admin
MONGO_INITDB_ROOT_PASSWORD: password123
MONGO_INITDB_DATABASE: myapp
ports:
- "27017:27017"
volumes:
- mongodb_data:/data/db
- mongodb_config:/data/configdb
networks:
- mongodb_network
mongo-express:
image: mongo-express:latest
container_name: mongo-express
restart: unless-stopped
ports:
- "8081:8081"
environment:
ME_CONFIG_MONGODB_ADMINUSERNAME: admin
ME_CONFIG_MONGODB_ADMINPASSWORD: password123
ME_CONFIG_MONGODB_URL: mongodb://admin:password123@mongodb:27017/
depends_on:
- mongodb
networks:
- mongodb_network
volumes:
mongodb_data:
mongodb_config:
networks:
mongodb_network:
driver: bridge
启动服务:
docker-compose up -d
6. 验证安装
# 查看容器运行状态
docker ps
# 查看日志
docker logs mongodb
# 测试连接
docker exec -it mongodb mongosh
基本配置
连接数据库
# 连接到本地 MongoDB
mongosh
# 连接到带认证的 MongoDB
mongosh --username admin --password password123 --authenticationDatabase admin
# 连接到远程 MongoDB
mongosh "mongodb://admin:password123@host:27017/database"
基本命令
// 显示数据库列表
show dbs
// 创建/切换数据库
use myapp
// 显示当前数据库
db
// 显示集合列表
show collections
// 显示当前用户
db.runCommand({connectionStatus: 1})
数据库操作
数据库管理
// 创建数据库(插入数据时自动创建)
use newdatabase
// 删除数据库
db.dropDatabase()
// 查看数据库统计信息
db.stats()
// 查看数据库大小
db.runCommand({dbStats: 1, scale: 1024*1024}) // MB
集合操作
集合管理
// 创建集合
db.createCollection("users")
// 创建带选项的集合
db.createCollection("users", {
validator: {
$jsonSchema: {
bsonType: "object",
required: ["name", "email"],
properties: {
name: {
bsonType: "string",
description: "must be a string and is required"
},
email: {
bsonType: "string",
pattern: "^[a-zA-Z0-9._%+-]+@[a-zA-Z0-9.-]+\\.[a-zA-Z]{2,}$",
description: "must be a valid email address"
}
}
}
}
})
// 删除集合
db.users.drop()
// 重命名集合
db.users.renameCollection("customers")
文档操作
插入文档
// 插入单个文档
db.users.insertOne({
name: "John Doe",
email: "john@example.com",
age: 30,
created: new Date()
})
// 插入多个文档
db.users.insertMany([
{name: "Alice", email: "alice@example.com", age: 25},
{name: "Bob", email: "bob@example.com", age: 35}
])
查询文档
// 查询所有文档
db.users.find()
// 条件查询
db.users.find({age: {$gte: 25}})
// 查询单个文档
db.users.findOne({name: "John Doe"})
// 投影查询(只返回指定字段)
db.users.find({}, {name: 1, email: 1, _id: 0})
// 排序和限制
db.users.find().sort({age: -1}).limit(10)
// 分页查询
db.users.find().skip(20).limit(10)
更新文档
// 更新单个文档
db.users.updateOne(
{name: "John Doe"},
{$set: {age: 31}}
)
// 更新多个文档
db.users.updateMany(
{age: {$lt: 30}},
{$set: {status: "young"}}
)
// 替换文档
db.users.replaceOne(
{name: "John Doe"},
{name: "John Doe", email: "john.doe@example.com", age: 31}
)
// Upsert 操作
db.users.updateOne(
{name: "Jane Doe"},
{$set: {email: "jane@example.com", age: 28}},
{upsert: true}
)
删除文档
// 删除单个文档
db.users.deleteOne({name: "John Doe"})
// 删除多个文档
db.users.deleteMany({age: {$lt: 18}})
// 删除所有文档
db.users.deleteMany({})
索引管理
创建索引
// 创建单字段索引
db.users.createIndex({email: 1})
// 创建复合索引
db.users.createIndex({name: 1, age: -1})
// 创建文本索引
db.users.createIndex({name: "text", description: "text"})
// 创建唯一索引
db.users.createIndex({email: 1}, {unique: true})
// 创建部分索引
db.users.createIndex(
{age: 1},
{partialFilterExpression: {age: {$gt: 18}}}
)
索引管理
// 查看索引
db.users.getIndexes()
// 查看索引统计
db.users.getIndexStats()
// 删除索引
db.users.dropIndex("email_1")
// 重建索引
db.users.reIndex()
聚合管道
基本聚合操作
// 简单聚合
db.users.aggregate([
{$match: {age: {$gte: 25}}},
{$group: {_id: "$department", avgAge: {$avg: "$age"}}},
{$sort: {avgAge: -1}}
])
// 复杂聚合
db.orders.aggregate([
{$match: {status: "completed"}},
{$unwind: "$items"},
{$group: {
_id: "$items.product",
totalQuantity: {$sum: "$items.quantity"},
totalRevenue: {$sum: {$multiply: ["$items.price", "$items.quantity"]}}
}},
{$sort: {totalRevenue: -1}},
{$limit: 10}
])
常用聚合阶段
// $lookup - 关联查询
db.users.aggregate([
{$lookup: {
from: "orders",
localField: "_id",
foreignField: "userId",
as: "userOrders"
}}
])
// $project - 字段投影
db.users.aggregate([
{$project: {
name: 1,
email: 1,
ageGroup: {
$cond: {
if: {$gte: ["$age", 30]},
then: "adult",
else: "young"
}
}
}}
])
用户权限管理
创建用户
// 创建数据库管理员
db.createUser({
user: "admin",
pwd: "password123",
roles: [{role: "dbAdmin", db: "myapp"}]
})
// 创建应用用户
db.createUser({
user: "appuser",
pwd: "apppass123",
roles: [{role: "readWrite", db: "myapp"}]
})
// 创建只读用户
db.createUser({
user: "readonly",
pwd: "readpass123",
roles: [{role: "read", db: "myapp"}]
})
用户管理
// 查看用户
db.getUsers()
// 修改用户密码
db.changeUserPassword("appuser", "newpassword123")
// 删除用户
db.dropUser("readonly")
// 授予角色
db.grantRolesToUser("appuser", [{role: "dbAdmin", db: "myapp"}])
副本集配置
启动副本集
# 创建 Docker 网络
docker network create mongo-network
# 启动多个 MongoDB 实例
docker run -d --name mongo1 --network mongo-network -p 27017:27017 mongo:latest --replSet rs0
docker run -d --name mongo2 --network mongo-network -p 27018:27017 mongo:latest --replSet rs0
docker run -d --name mongo3 --network mongo-network -p 27019:27017 mongo:latest --replSet rs0
配置副本集
// 初始化副本集
rs.initiate({
_id: "rs0",
members: [
{_id: 0, host: "mongo1:27017"},
{_id: 1, host: "mongo2:27017"},
{_id: 2, host: "mongo3:27017"}
]
})
// 查看副本集状态
rs.status()
// 添加成员
rs.add("localhost:27020")
// 移除成员
rs.remove("localhost:27020")
MongoDB 事务
事务概述
MongoDB 从 4.0 版本开始支持多文档事务,4.2 版本支持分片集群事务。事务提供了 ACID 保证:
- 原子性(Atomicity):事务中的所有操作要么全部成功,要么全部失败
- 一致性(Consistency):事务前后数据库状态保持一致
- 隔离性(Isolation):并发事务相互隔离
- 持久性(Durability):事务提交后,数据永久保存
单文档事务
MongoDB 的单文档操作本身就是原子性的:
// 单文档原子更新
db.accounts.updateOne(
{ _id: "account1" },
{
$inc: { balance: -100 },
$push: { transactions: { amount: -100, date: new Date() } }
}
)
多文档事务
基本用法
// 开启会话
const session = db.getClient().startSession()
// 开始事务
session.startTransaction({
readConcern: { level: "snapshot" },
writeConcern: { w: "majority" },
readPreference: "primary"
})
try {
// 在事务中执行操作
const accountsColl = session.getDatabase("bank").accounts
const transactionsColl = session.getDatabase("bank").transactions
// 扣款
accountsColl.updateOne(
{ _id: "account1" },
{ $inc: { balance: -100 } },
{ session }
)
// 收款
accountsColl.updateOne(
{ _id: "account2" },
{ $inc: { balance: 100 } },
{ session }
)
// 记录交易
transactionsColl.insertOne({
from: "account1",
to: "account2",
amount: 100,
timestamp: new Date()
}, { session })
// 提交事务
session.commitTransaction()
print("Transaction committed successfully")
} catch (error) {
// 回滚事务
session.abortTransaction()
print("Transaction aborted due to error: " + error)
} finally {
// 结束会话
session.endSession()
}
事务选项
// 事务配置选项
const transactionOptions = {
readConcern: { level: "snapshot" }, // 读一致性级别
writeConcern: { w: "majority" }, // 写确认级别
readPreference: "primary", // 读偏好
maxCommitTimeMS: 1000 // 最大提交时间
}
session.startTransaction(transactionOptions)
事务最佳实践
-
保持事务简短
// 好的实践:只包含必要的操作 await session.withTransaction(async () => { await accounts.updateOne(...) await transactions.insertOne(...) }) // 避免:长时间运行的事务 await session.withTransaction(async () => { const data = await fetchFromExternalAPI() // 避免外部调用 await processComplexData(data) // 避免复杂计算 }) -
处理事务冲突
async function transferWithRetry(fromAccount, toAccount, amount) { const maxRetries = 3 let retries = 0 while (retries < maxRetries) { try { await session.withTransaction(async () => { // 转账逻辑 }) break } catch (error) { if (error.hasErrorLabel("TransientTransactionError")) { retries++ await new Promise(resolve => setTimeout(resolve, 100 * retries)) } else { throw error } } } } -
事务隔离级别
// 设置不同的隔离级别 session.startTransaction({ readConcern: { level: "snapshot" // 快照隔离(推荐) // level: "majority" // 多数确认 // level: "local" // 本地读取 } })
MongoDB 分片集群
分片架构概述
MongoDB 分片集群由以下组件组成:
┌─────────────────────────────────────────────┐
│ 应用程序 │
└─────────────────┬───────────────────────────┘
│
┌─────────────────▼───────────────────────────┐
│ mongos 路由器 │
│ (可以有多个实例) │
└─────────────────┬───────────────────────────┘
│
┌─────────┴─────────┬─────────────┐
│ │ │
┌───────▼──────┐ ┌────────▼──────┐ ┌───▼────────┐
│ 分片1 │ │ 分片2 │ │ 配置服务器 │
│ (副本集) │ │ (副本集) │ │ (副本集) │
└──────────────┘ └───────────────┘ └────────────┘
分片集群部署
1. 部署配置服务器副本集
# 创建配置服务器目录
mkdir -p /data/configsvr/{1,2,3}
# 启动配置服务器
mongod --configsvr --replSet configRS --port 27019 \
--dbpath /data/configsvr/1 --bind_ip 0.0.0.0
mongod --configsvr --replSet configRS --port 27020 \
--dbpath /data/configsvr/2 --bind_ip 0.0.0.0
mongod --configsvr --replSet configRS --port 27021 \
--dbpath /data/configsvr/3 --bind_ip 0.0.0.0
# 初始化配置服务器副本集
mongo --port 27019
rs.initiate({
_id: "configRS",
configsvr: true,
members: [
{ _id: 0, host: "localhost:27019" },
{ _id: 1, host: "localhost:27020" },
{ _id: 2, host: "localhost:27021" }
]
})
2. 部署分片副本集
# 分片1
mkdir -p /data/shard1/{1,2,3}
mongod --shardsvr --replSet shard1RS --port 27022 \
--dbpath /data/shard1/1 --bind_ip localhost,<ip>
mongod --shardsvr --replSet shard1RS --port 27023 \
--dbpath /data/shard1/2 --bind_ip localhost,<ip>
mongod --shardsvr --replSet shard1RS --port 27024 \
--dbpath /data/shard1/3 --bind_ip localhost,<ip>
# 初始化分片1副本集
mongo --port 27022
rs.initiate({
_id: "shard1RS",
members: [
{ _id: 0, host: "localhost:27022" },
{ _id: 1, host: "localhost:27023" },
{ _id: 2, host: "localhost:27024" }
]
})
# 分片2(类似配置)
# ...
3. 部署 mongos 路由器
# 启动 mongos
mongos --configdb configRS/confighost1:27019,confighost2:27020,confighost3:27021 \
--port 27017 \
--bind_ip 0.0.0.0 \
--logpath /var/log/mongodb/mongos.log
# 连接到 mongos 并添加分片
mongosh --port 27017
# 添加分片
sh.addShard("shard1RS/localhost:27022,localhost:27023,localhost:27024")
sh.addShard("shard2RS/localhost:27025,localhost:27026,localhost:27027")
# 查看分片状态
sh.status()
分片策略
1. 范围分片(Range Sharding)
// 为数据库启用分片
sh.enableSharding("myapp")
// 创建索引
db.users.createIndex({ userId: 1 })
// 对集合进行范围分片
sh.shardCollection("myapp.users", { userId: 1 })
// 查看分片分布
db.users.getShardDistribution()
2. 哈希分片(Hash Sharding)
// 创建哈希索引
db.orders.createIndex({ orderId: "hashed" })
// 对集合进行哈希分片
sh.shardCollection("myapp.orders", { orderId: "hashed" })
// 预分割块(可选,用于均匀分布)
sh.splitAt("myapp.orders", { orderId: 0 })
sh.splitAt("myapp.orders", { orderId: Long("2305843009213693952") })
3. 复合分片键
// 创建复合索引
db.events.createIndex({ customerId: 1, timestamp: 1 })
// 使用复合键分片
sh.shardCollection("myapp.events", { customerId: 1, timestamp: 1 })
// 设置块大小(默认64MB)
use config
db.settings.updateOne(
{ _id: "chunksize" },
{ $set: { value: 128 } },
{ upsert: true }
) // 128MB
分片键选择策略
好的分片键特征
- 高基数:有大量不同的值
- 写分布均匀:避免热点
- 查询隔离:常用查询能定位到特定分片
分片键设计模式
// 1. 单调递增问题及解决
// 问题:使用 _id 或时间戳导致热点
sh.shardCollection("logs.access", { _id: 1 }) // 不推荐
// 解决方案:组合键或哈希
sh.shardCollection("logs.access", { timestamp: 1, serverId: 1 }) // 推荐
sh.shardCollection("logs.access", { _id: "hashed" }) // 推荐
// 2. 地理分片
sh.shardCollection("stores.locations", { region: 1, storeId: 1 })
// 3. 标签感知分片
sh.addShardTag("shard1RS", "US")
sh.addShardTag("shard2RS", "EU")
sh.addShardTag("shard3RS", "ASIA")
sh.addTagRange("myapp.users",
{ region: "US", userId: MinKey() },
{ region: "US", userId: MaxKey() },
"US"
)
分片集群管理
1. 监控分片状态
// 查看集群状态
sh.status()
// 查看块分布
db.chunks.find({ ns: "myapp.users" }).pretty()
// 查看迁移状态
sh.isBalancerRunning()
db.locks.find({ _id: "balancer" })
// 查看分片统计
db.users.getShardDistribution()
2. 平衡器管理
// 停止平衡器
sh.stopBalancer()
// 启动平衡器
sh.startBalancer()
// 设置平衡器窗口
use config
db.settings.update(
{ _id: "balancer" },
{ $set: {
activeWindow: {
start: "23:00",
stop: "06:00"
}
}},
{ upsert: true }
)
// 手动移动块
sh.moveChunk(
"myapp.users",
{ userId: 100000 },
"shard2RS"
)
3. 分片维护
// 添加新分片
sh.addShard("shard3RS/host1:27017,host2:27017,host3:27017")
// 删除分片(数据会自动迁移)
use admin
db.runCommand({ removeShard: "shard3RS" })
// 检查删除进度
db.runCommand({ removeShard: "shard3RS" })
分片性能优化
1. 查询优化
// 包含分片键的查询(定向查询)
db.users.find({ userId: 12345, name: "John" }) // 快速
// 不包含分片键的查询(广播查询)
db.users.find({ name: "John" }) // 慢,需要查询所有分片
// 使用 explain 分析
db.users.find({ userId: 12345 }).explain("executionStats")
2. 批量操作优化
// 批量插入时排序以提高效率
const bulkOps = users
.sort((a, b) => a.userId - b.userId) // 按分片键排序
.map(user => ({ insertOne: { document: user } }))
db.users.bulkWrite(bulkOps, { ordered: false })
3. 读取偏好设置
// 从最近的节点读取
db.users.find().readPref("nearest")
// 从从节点读取
db.users.find().readPref("secondary")
// 标签感知读取
db.users.find().readPref(
"secondary",
[{ region: "US" }]
)
分片集群故障处理
1. 配置服务器故障
// 如果配置服务器不可用,集群变为只读
// 恢复步骤:
// 1. 修复配置服务器副本集
// 2. 确保多数节点可用
// 3. 重启 mongos
2. 分片故障
// 单个分片故障影响:
// - 该分片数据不可访问
// - 写入该分片的操作失败
// - 可以设置读取偏好绕过
// 恢复步骤:
// 1. 修复分片副本集
// 2. 运行数据一致性检查
db.runCommand({ dbCheck: 1 })
3. 数据迁移问题
// 清理孤儿文档
db.runCommand({
cleanupOrphaned: "myapp.users",
startingFromKey: { userId: MinKey() },
secondaryThrottle: true
})
// 强制完成迁移
sh.stopBalancer()
sh.waitForBalancer(false)
// 手动处理后
sh.startBalancer()
查询优化
// 使用 explain() 分析查询
db.users.find({age: {$gte: 25}}).explain("executionStats")
// 使用 hint() 强制使用索引
db.users.find({name: "John"}).hint({name: 1})
// 创建合适的索引
db.users.createIndex({age: 1, status: 1})
连接池配置
// 连接字符串配置
const url = "mongodb://localhost:27017/myapp?maxPoolSize=50&minPoolSize=5&maxIdleTimeMS=30000"
批量操作
// 批量插入
db.users.insertMany(documents, {ordered: false})
// 批量写入操作
db.users.bulkWrite([
{insertOne: {document: {name: "Alice", age: 25}}},
{updateOne: {filter: {name: "Bob"}, update: {$set: {age: 26}}}},
{deleteOne: {filter: {name: "Charlie"}}}
])
监控维护
数据库统计
// 数据库统计信息
db.stats()
// 集合统计信息
db.users.stats()
// 索引统计
db.users.aggregate([{$indexStats: {}}])
// 服务器状态
db.serverStatus()
性能监控
// 当前操作
db.currentOp()
// 查看慢查询
db.getProfilingStatus()
// 启用分析器
db.setProfilingLevel(1, {slowms: 100})
// 查看分析结果
db.system.profile.find().sort({ts: -1}).limit(5)
备份恢复
# 备份数据库
mongodump --host localhost:27017 --db myapp --out /backup/
# 恢复数据库
mongorestore --host localhost:27017 --db myapp /backup/myapp/
# 导出集合
mongoexport --host localhost:27017 --db myapp --collection users --out users.json
# 导入集合
mongoimport --host localhost:27017 --db myapp --collection users --file users.json
常见问题
问题1:连接超时
现象: 连接 MongoDB 时出现超时错误 原因: 网络配置或防火墙问题 解决方案:
# 检查端口是否开放
telnet localhost 27017
# 检查防火墙设置
sudo ufw status
# 检查 MongoDB 绑定地址
docker exec -it mongodb cat /etc/mongod.conf
问题2:认证失败
现象: 用户名密码正确但认证失败 原因: 认证数据库不正确 解决方案:
// 确保使用正确的认证数据库
mongosh --authenticationDatabase admin --username admin --password password123
问题3:性能问题
现象: 查询速度缓慢 原因: 缺少合适的索引 解决方案:
// 分析查询计划
db.users.find({age: {$gte: 25}}).explain("executionStats")
// 创建合适的索引
db.users.createIndex({age: 1})
⚠️ 注意: 过多的索引会影响写入性能,需要根据实际查询模式平衡索引数量。