MongoDB 中文教程

1. 核心概念

MongoDB 是一个开源的文档数据库,属于 NoSQL 数据库的一种。它使用 BSON(Binary JSON)格式存储数据,提供了灵活的数据模型和强大的查询能力。

1.1 主要特点

  • 文档模型:使用类似 JSON 的文档存储数据,结构灵活
  • 无模式:不需要预定义表结构,支持动态添加字段
  • 查询能力:支持丰富的查询操作,包括范围查询、正则表达式、聚合等
  • 索引支持:支持多种类型的索引,提高查询性能
  • 复制集:提供高可用性和数据冗余
  • 分片:支持水平扩展,处理大规模数据
  • 聚合框架:支持复杂的数据聚合操作
  • 地理空间查询:支持地理位置数据和查询

1.2 技术栈特点

  • 文档存储:使用 BSON 格式存储数据,结构灵活
  • 分布式架构:支持复制集和分片,提供高可用性和扩展性
  • 高性能:内存映射存储引擎,提供快速的读写操作
  • 可扩展性:支持水平扩展,处理大规模数据
  • 丰富的查询功能:支持复杂的查询操作和聚合

2. 安装配置

2.1 安装

2.1.1 Windows 安装

  1. MongoDB 官方网站 下载 Windows 版本的 MongoDB
  2. 运行安装程序,按照向导完成安装
  3. 配置环境变量,将 MongoDB 的 bin 目录添加到 PATH 中
  4. 创建数据目录和日志目录:
    mkdir -p C:\data\db
    mkdir -p C:\data\log
  5. 启动 MongoDB 服务:
    mongod --dbpath=C:\data\db --logpath=C:\data\log\mongod.log --logappend

2.1.2 Linux 安装

使用包管理器安装:

# Ubuntu/Debian
wget -qO - https://www.mongodb.org/static/pgp/server-6.0.asc | sudo apt-key add -
echo "deb [ arch=amd64,arm64 ] https://repo.mongodb.org/apt/ubuntu focal/mongodb-org/6.0 multiverse" | sudo tee /etc/apt/sources.list.d/mongodb-org-6.0.list
sudo apt-get update
sudo apt-get install -y mongodb-org

# 启动服务
sudo systemctl start mongod

# 设置开机自启
sudo systemctl enable mongod

# CentOS/RHEL
sudo tee /etc/yum.repos.d/mongodb-org-6.0.repo << 'EOF'
[mongodb-org-6.0]
name=MongoDB Repository
baseurl=https://repo.mongodb.org/yum/redhat/$releasever/mongodb-org/6.0/x86_64/
gpgcheck=1
enabled=1
gpgkey=https://www.mongodb.org/static/pgp/server-6.0.asc
EOF
sudo yum install -y mongodb-org

# 启动服务
sudo systemctl start mongod

# 设置开机自启
sudo systemctl enable mongod

2.1.3 macOS 安装

使用 Homebrew 安装:

brew tap mongodb/brew
brew install mongodb-community

# 启动服务
brew services start mongodb-community

2.2 基本配置

MongoDB 的配置文件通常位于 /etc/mongod.conf(Linux)或 MongoDB 安装目录下的 mongod.conf(Windows/macOS)。

主要配置项:

# 存储配置
storage:
  dbPath: /var/lib/mongodb
  journal:
    enabled: true

# 网络配置
net:
  port: 27017
bindIp: 127.0.0.1

# 复制集配置
replication:
  replSetName: "rs0"

# 分片配置
sharding:
  clusterRole: "shardsvr"

# 安全配置
security:
  authorization: "enabled"

2.3 启动 MongoDB

2.3.1 启动服务器

# 使用默认配置启动
mongod

# 使用指定配置文件启动
mongod --config /etc/mongod.conf

# 以守护进程方式启动
mongod --fork --logpath /var/log/mongodb/mongod.log

2.3.2 启动客户端

# 基本连接
mongo

# 指定主机和端口
mongo --host host --port port

# 连接到指定数据库
mongo database

# 使用用户名和密码连接
mongo -u username -p password --authenticationDatabase admin

3. 基本使用

3.1 数据库操作

# 查看当前数据库
db

# 切换数据库(如果不存在则创建)
use database_name

# 查看所有数据库
show dbs

# 删除当前数据库
db.dropDatabase()

3.2 集合操作

# 创建集合
db.createCollection("collection_name")

# 查看集合
show collections

# 删除集合
db.collection_name.drop()

# 重命名集合
db.collection_name.renameCollection("new_collection_name")

3.3 文档操作

3.3.1 插入文档

# 插入单个文档
db.collection_name.insertOne({
  name: "张三",
  age: 30,
  email: "zhangsan@example.com"
})

# 插入多个文档
db.collection_name.insertMany([
  {
    name: "张三",
    age: 30,
    email: "zhangsan@example.com"
  },
  {
    name: "李四",
    age: 25,
    email: "lisi@example.com"
  }
])

# 旧版插入方法(兼容)
db.collection_name.insert({
  name: "张三",
  age: 30,
  email: "zhangsan@example.com"
})

3.3.2 查询文档

# 查询所有文档
db.collection_name.find()

# 格式化查询结果
db.collection_name.find().pretty()

# 按条件查询
db.collection_name.find({ age: 30 })

# 条件操作符查询
db.collection_name.find({ age: { $gt: 25 } })  # 大于 25
db.collection_name.find({ age: { $gte: 25 } }) # 大于等于 25
db.collection_name.find({ age: { $lt: 35 } })  # 小于 35
db.collection_name.find({ age: { $lte: 35 } }) # 小于等于 35
db.collection_name.find({ age: { $ne: 30 } })  # 不等于 30
db.collection_name.find({ age: { $in: [25, 30, 35] } })  # 在数组中
db.collection_name.find({ age: { $nin: [25, 30, 35] } }) # 不在数组中

# 逻辑操作符查询
db.collection_name.find({ $and: [{ age: { $gt: 25 } }, { age: { $lt: 35 } }] })  # 与
db.collection_name.find({ $or: [{ age: { $lt: 25 } }, { age: { $gt: 35 } }] })   # 或
db.collection_name.find({ $not: { age: { $eq: 30 } } })                           # 非

# 字段操作符查询
db.collection_name.find({ name: { $exists: true } })  # 字段存在
db.collection_name.find({ name: { $type: "string" } }) # 字段类型

# 正则表达式查询
db.collection_name.find({ name: /^张/ })  # 以张开头
db.collection_name.find({ name: /三$/ })  # 以三结尾
db.collection_name.find({ name: /李/ })   # 包含李

# 限制和跳过
db.collection_name.find().limit(10)     # 限制返回 10 条
db.collection_name.find().skip(5)       # 跳过前 5 条
db.collection_name.find().skip(5).limit(10) # 跳过前 5 条,返回 10 条

# 排序
db.collection_name.find().sort({ age: 1 })   # 升序
db.collection_name.find().sort({ age: -1 })  # 降序

# 投影(只返回指定字段)
db.collection_name.find({}, { name: 1, age: 1 })  # 返回 name 和 age 字段
db.collection_name.find({}, { _id: 0, name: 1 })  # 不返回 _id 字段,返回 name 字段

3.3.3 更新文档

# 更新单个文档
db.collection_name.updateOne(
  { name: "张三" },
  {
    $set: { age: 31, email: "zhangsanupdated@example.com" },
    $currentDate: { lastModified: true }
  }
)

# 更新多个文档
db.collection_name.updateMany(
  { age: { $lt: 30 } },
  {
    $set: { status: "young" }
  }
)

# 替换文档
db.collection_name.replaceOne(
  { name: "张三" },
  {
    name: "张三",
    age: 32,
    email: "zhangsan@example.com",
    address: "北京市"
  }
)

# 旧版更新方法(兼容)
db.collection_name.update(
  { name: "张三" },
  {
    $set: { age: 31 }
  }
)

# 更新操作符
# $set: 设置字段值
# $unset: 删除字段
# $inc: 增加字段值
# $mul: 乘以字段值
# $rename: 重命名字段
# $min: 仅当新值小于当前值时设置
# $max: 仅当新值大于当前值时设置
# $currentDate: 设置为当前日期

3.3.4 删除文档

# 删除单个文档
db.collection_name.deleteOne({ name: "张三" })

# 删除多个文档
db.collection_name.deleteMany({ age: { $lt: 25 } })

# 旧版删除方法(兼容)
db.collection_name.remove({ name: "张三" })
db.collection_name.remove({ age: { $lt: 25 } }, { justOne: true }) # 仅删除一个

# 删除所有文档(保留集合)
db.collection_name.deleteMany({})

3.4 索引操作

# 创建单字段索引
db.collection_name.createIndex({ age: 1 })  # 升序
db.collection_name.createIndex({ age: -1 }) # 降序

# 创建复合索引
db.collection_name.createIndex({ name: 1, age: -1 })

# 创建唯一索引
db.collection_name.createIndex({ email: 1 }, { unique: true })

# 创建文本索引
db.collection_name.createIndex({ content: "text" })

# 创建地理空间索引
db.collection_name.createIndex({ location: "2dsphere" })

# 查看索引
db.collection_name.getIndexes()

# 删除索引
db.collection_name.dropIndex("index_name")
db.collection_name.dropIndex({ age: 1 }) # 通过字段删除

# 删除所有索引(保留 _id 索引)
db.collection_name.dropIndexes()

4. 高级功能

4.1 聚合框架

聚合框架用于处理数据聚合操作,如分组、计数、求和等。

# 基本聚合
db.collection_name.aggregate([
  { $match: { age: { $gt: 25 } } },  # 过滤条件
  { $group: { _id: "$age", count: { $sum: 1 } } },  # 按年龄分组,计算数量
  { $sort: { count: -1 } }  # 按数量降序排序
])

# 常用聚合操作符
# $match: 过滤文档
# $group: 分组文档
# $sort: 排序文档
# $limit: 限制文档数量
# $skip: 跳过文档
# $project: 投影字段
# $unwind: 展开数组字段
# $lookup: 关联查询(类似 SQL JOIN)
# $addFields: 添加新字段
# $out: 将结果输出到新集合

# 示例:计算每个年龄段的平均年龄和人数
db.collection_name.aggregate([
  { $group: {
      _id: { $floor: { $divide: ["$age", 10] } },  # 按年龄分组(每 10 岁一组)
      averageAge: { $avg: "$age" },  # 计算平均年龄
      count: { $sum: 1 }  # 计算人数
    }
  },
  { $sort: { _id: 1 } }  # 按年龄段升序排序
])

# 示例:关联查询
db.orders.aggregate([
  {
    $lookup: {
      from: "customers",
      localField: "customerId",
      foreignField: "_id",
      as: "customer"
    }
  }
])

4.2 复制集

复制集提供高可用性和数据冗余,由一个主节点和多个从节点组成。

4.2.1 配置复制集

  1. 启动多个 MongoDB 实例:
# 实例 1(主节点)
mongod --port 27017 --dbpath /data/db1 --replSet rs0

# 实例 2(从节点)
mongod --port 27018 --dbpath /data/db2 --replSet rs0

# 实例 3(从节点)
mongod --port 27019 --dbpath /data/db3 --replSet rs0
  1. 初始化复制集:
mongo --port 27017

rs.initiate({
  _id: "rs0",
  members: [
    { _id: 0, host: "localhost:27017" },
    { _id: 1, host: "localhost:27018" },
    { _id: 2, host: "localhost:27019" }
  ]
})

4.2.2 管理复制集

# 查看复制集状态
rs.status()

# 查看复制集配置
rs.conf()

# 添加节点
rs.add("localhost:27020")

# 移除节点
rs.remove("localhost:27020")

# 手动故障转移
rs.stepDown()

# 强制重新配置
rs.reconfig(config, { force: true })

4.3 分片

分片用于水平扩展,将数据分布到多个服务器上。

4.3.1 配置分片

  1. 启动配置服务器:
mongod --configsvr --replSet configrs --port 27019 --dbpath /data/configdb
  1. 启动分片服务器:
mongod --shardsvr --replSet shard1rs --port 27017 --dbpath /data/shard1
mongod --shardsvr --replSet shard2rs --port 27018 --dbpath /data/shard2
  1. 启动 mongos 路由器:
mongos --configdb configrs/localhost:27019 --port 27020
  1. 配置分片:
mongo --port 27020

# 添加分片
sh.addShard("shard1rs/localhost:27017")
sh.addShard("shard2rs/localhost:27018")

# 启用数据库分片
sh.enableSharding("database_name")

# 配置集合分片键
sh.shardCollection("database_name.collection_name", { "_id": "hashed" })
sh.shardCollection("database_name.collection_name", { "age": 1 })

4.4 地理空间查询

MongoDB 支持地理位置数据和查询。

# 插入地理空间数据
db.places.insertMany([
  {
    name: "北京",
    location: {
      type: "Point",
      coordinates: [116.4074, 39.9042]
    }
  },
  {
    name: "上海",
    location: {
      type: "Point",
      coordinates: [121.4737, 31.2304]
    }
  }
])

# 创建地理空间索引
db.places.createIndex({ location: "2dsphere" })

# 查询附近的位置
db.places.find({
  location: {
    $near: {
      $geometry: {
        type: "Point",
        coordinates: [116.4074, 39.9042]
      },
      $maxDistance: 100000  # 100 公里
    }
  }
})

# 查询指定区域内的位置
db.places.find({
  location: {
    $geoWithin: {
      $geometry: {
        type: "Polygon",
        coordinates: [
          [
            [116.0, 39.5],
            [117.0, 39.5],
            [117.0, 40.0],
            [116.0, 40.0],
            [116.0, 39.5]
          ]
        ]
      }
    }
  }
})

4.5 事务

MongoDB 4.0+ 支持多文档事务。

// 开始事务
session = db.getMongo().startSession()
transactionOptions = {
  readPreference: "primary",
  readConcern: { level: "local" },
  writeConcern: { w: "majority" }
}

session.startTransaction(transactionOptions)

try {
  // 操作 1
  db = session.getDatabase("test")
  db.accounts.updateOne({ _id: 1 }, { $inc: { balance: -100 } })
  
  // 操作 2
  db.accounts.updateOne({ _id: 2 }, { $inc: { balance: 100 } })
  
  // 提交事务
  session.commitTransaction()
  print("事务提交成功")
} catch (error) {
  print("事务失败:", error)
  session.abortTransaction()
} finally {
  session.endSession()
}

5. 最佳实践

5.1 数据模型设计

  • 文档结构:根据应用访问模式设计文档结构,避免过度嵌套
  • 字段命名:使用有意义的字段名,避免使用保留字
  • 数据类型:选择合适的数据类型,提高存储效率
  • 引用 vs 嵌入:根据数据访问模式选择合适的关系表示方式
    • 嵌入:适合一对一或一对多关系,数据访问频繁
    • 引用:适合多对多关系,数据大小较大

5.2 性能优化

5.2.1 索引优化

  • 创建合适的索引:根据查询模式创建索引
  • 复合索引:使用复合索引覆盖多个查询条件
  • 索引顺序:将选择性高的字段放在索引前面
  • 避免过度索引:过多的索引会影响写入性能
  • 定期重建索引:对于频繁更新的集合,定期重建索引

5.2.2 查询优化

  • 使用投影:只返回需要的字段
  • 避免全表扫描:使用索引覆盖查询
  • 限制结果集大小:使用 limit() 限制返回的文档数量
  • 避免使用 $where:$where 操作符会降低查询性能
  • 使用聚合框架:对于复杂查询,使用聚合框架而非多个查询

5.2.3 写入优化

  • 批量操作:使用 insertMany() 批量插入文档
  • 有序 vs 无序:对于不需要顺序的批量操作,使用 ordered: false
  • 批量更新:使用 updateMany() 批量更新文档
  • 避免频繁更新:减少小的、频繁的更新操作
  • 使用 upsert:对于可能存在的文档,使用 upsert 避免额外的查询

5.3 安全性

5.3.1 访问控制

  • 启用认证:在生产环境中启用 MongoDB 认证
  • 创建用户:为不同的应用创建不同的用户,分配适当的权限
  • 使用角色:使用内置角色或自定义角色管理权限
  • 最小权限原则:只授予应用所需的最小权限

5.3.2 数据安全

  • 加密:使用 WiredTiger 存储引擎的加密功能
  • 备份:定期备份数据库
  • 复制集:使用复制集提供数据冗余
  • 防火墙:限制 MongoDB 端口的访问
  • 网络加密:使用 TLS/SSL 加密网络通信

5.4 监控和维护

5.4.1 监控

  • MongoDB Compass:使用官方 GUI 工具监控数据库
  • mongostat:实时监控数据库状态
  • mongotop:监控集合级别的操作时间
  • **db.serverStatus()**:查看服务器状态
  • **db.stats()**:查看数据库统计信息
  • **db.collection.stats()**:查看集合统计信息

5.4.2 维护

  • 定期备份:使用 mongodump 定期备份数据库
  • 索引重建:定期重建索引,提高查询性能
  • 碎片整理:对于 WiredTiger 存储引擎,定期运行 compact 命令
  • 日志轮换:配置适当的日志轮换策略
  • 监控磁盘空间:确保有足够的磁盘空间

6. 实际应用

6.1 Node.js 应用集成

使用 MongoDB Node.js 驱动程序集成 MongoDB。

6.1.1 安装驱动

npm install mongodb

6.1.2 基本使用

const { MongoClient } = require('mongodb');

// 连接字符串
const uri = 'mongodb://localhost:27017';
const client = new MongoClient(uri);

async function run() {
  try {
    // 连接到 MongoDB
    await client.connect();
    console.log('已连接到 MongoDB');
    
    // 选择数据库和集合
    const database = client.db('test');
    const collection = database.collection('users');
    
    // 插入文档
    const insertResult = await collection.insertOne({
      name: '张三',
      age: 30,
      email: 'zhangsan@example.com'
    });
    console.log('插入结果:', insertResult);
    
    // 查询文档
    const findResult = await collection.find({ age: { $gt: 25 } }).toArray();
    console.log('查询结果:', findResult);
    
    // 更新文档
    const updateResult = await collection.updateOne(
      { name: '张三' },
      { $set: { age: 31 } }
    );
    console.log('更新结果:', updateResult);
    
    // 删除文档
    const deleteResult = await collection.deleteOne({ name: '张三' });
    console.log('删除结果:', deleteResult);
    
  } finally {
    // 关闭连接
    await client.close();
    console.log('已关闭 MongoDB 连接');
  }
}

run().catch(console.dir);

6.2 Express.js 应用集成

使用 Mongoose ODM 集成 MongoDB。

6.2.1 安装依赖

npm install express mongoose

6.2.2 基本使用

const express = require('express');
const mongoose = require('mongoose');

const app = express();
const port = 3000;

// 连接 MongoDB
mongoose.connect('mongodb://localhost:27017/test', {
  useNewUrlParser: true,
  useUnifiedTopology: true
});

// 定义模型
const User = mongoose.model('User', {
  name: String,
  age: Number,
  email: String
});

// 中间件
app.use(express.json());

// 路由
app.get('/users', async (req, res) => {
  try {
    const users = await User.find();
    res.json(users);
  } catch (error) {
    res.status(500).json({ error: error.message });
  }
});

app.post('/users', async (req, res) => {
  try {
    const user = new User(req.body);
    await user.save();
    res.status(201).json(user);
  } catch (error) {
    res.status(400).json({ error: error.message });
  }
});

app.get('/users/:id', async (req, res) => {
  try {
    const user = await User.findById(req.params.id);
    if (!user) {
      return res.status(404).json({ message: '用户不存在' });
    }
    res.json(user);
  } catch (error) {
    res.status(500).json({ error: error.message });
  }
});

app.put('/users/:id', async (req, res) => {
  try {
    const user = await User.findByIdAndUpdate(req.params.id, req.body, { new: true });
    if (!user) {
      return res.status(404).json({ message: '用户不存在' });
    }
    res.json(user);
  } catch (error) {
    res.status(400).json({ error: error.message });
  }
});

app.delete('/users/:id', async (req, res) => {
  try {
    const user = await User.findByIdAndDelete(req.params.id);
    if (!user) {
      return res.status(404).json({ message: '用户不存在' });
    }
    res.json({ message: '用户已删除' });
  } catch (error) {
    res.status(500).json({ error: error.message });
  }
});

// 启动服务器
app.listen(port, () => {
  console.log(`服务器运行在 http://localhost:${port}`);
});

6.3 数据备份和恢复

6.3.1 备份数据

使用 mongodump 工具备份数据:

# 备份所有数据库
mongodump

# 备份指定数据库
mongodump --db database_name

# 备份指定集合
mongodump --db database_name --collection collection_name

# 备份到指定目录
mongodump --out /backup/directory

# 使用用户名和密码备份
mongodump -u username -p password --authenticationDatabase admin

6.3.2 恢复数据

使用 mongorestore 工具恢复数据:

# 恢复所有数据库
mongorestore

# 恢复指定数据库
mongorestore --db database_name /backup/directory/database_name

# 恢复指定集合
mongorestore --db database_name --collection collection_name /backup/directory/database_name/collection_name.bson

# 使用用户名和密码恢复
mongorestore -u username -p password --authenticationDatabase admin

6.4 监控工具

6.4.1 MongoDB Compass

MongoDB Compass 是官方的 GUI 工具,提供可视化的数据库管理和监控功能。

6.4.2 mongostat

mongostat 提供实时的数据库状态监控:

# 基本监控
mongostat

# 监控指定主机和端口
mongostat --host host --port port

# 监控间隔
mongostat 10  # 每 10 秒监控一次

6.4.3 mongotop

mongotop 提供集合级别的操作时间监控:

# 基本监控
mongotop

# 监控间隔
mongotop 10  # 每 10 秒监控一次

7. 总结

MongoDB 是一个功能强大的文档数据库,具有灵活的数据模型、丰富的查询功能和良好的可扩展性。本文介绍了 MongoDB 的核心概念、安装配置、基本使用、高级功能、最佳实践和实际应用示例,希望能够帮助开发者更好地理解和使用 MongoDB。

7.1 主要优势

  • 灵活的文档模型:使用类似 JSON 的文档存储数据,结构灵活
  • 强大的查询能力:支持丰富的查询操作和聚合
  • 高可用性:通过复制集提供高可用性和数据冗余
  • 可扩展性:通过分片支持水平扩展
  • 丰富的生态系统:提供多种语言的驱动程序和工具
  • 地理空间支持:支持地理位置数据和查询
  • 事务支持:支持多文档事务

7.2 适用场景

  • 内容管理系统:灵活的文档模型适合存储各种类型的内容
  • 实时分析:支持复杂的聚合操作和实时查询
  • 移动应用后端:灵活的数据模型适合快速迭代的移动应用
  • 物联网:支持存储和查询设备数据
  • 社交网络:适合存储用户关系和社交数据
  • 电子商务:支持存储产品信息和订单数据
  • 游戏:适合存储游戏状态和用户数据

7.3 未来展望

MongoDB 作为一个成熟的开源项目,不断发展和完善。未来的 MongoDB 可能会:

  • 进一步提高性能:优化存储引擎和查询执行
  • 增强安全性:提供更多的安全特性和合规性支持
  • 改进分片功能:提供更简单的分片管理和更灵活的分片策略
  • 增强云集成:提供更好的云服务集成和管理工具
  • 支持更多的数据类型:添加对新数据类型的支持

通过本文的学习,相信开发者已经对 MongoDB 有了全面的了解,可以开始在实际项目中使用 MongoDB 构建高性能、可扩展的应用了。

« 上一篇 Redis 中文教程 下一篇 » PostgreSQL 中文教程