全部笔记All notes

MongoDB 文档数据库完整指南

阅读 11m 37s11m 37s read

概述

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)

事务最佳实践

  1. 保持事务简短

    // 好的实践:只包含必要的操作
    await session.withTransaction(async () => {
      await accounts.updateOne(...)
      await transactions.insertOne(...)
    })
    
    // 避免:长时间运行的事务
    await session.withTransaction(async () => {
      const data = await fetchFromExternalAPI() // 避免外部调用
      await processComplexData(data)           // 避免复杂计算
    })
  2. 处理事务冲突

    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
          }
        }
      }
    }
  3. 事务隔离级别

    // 设置不同的隔离级别
    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. 高基数:有大量不同的值
  2. 写分布均匀:避免热点
  3. 查询隔离:常用查询能定位到特定分片

分片键设计模式

// 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})

⚠️ 注意: 过多的索引会影响写入性能,需要根据实际查询模式平衡索引数量。

相关文章

数据库相关

应用集成

运维部署

监控运维

大数据集成