{T}

MongoDB 基础操作

本文档介绍 MongoDB 的 CRUD 操作、高级查询、索引和聚合框架。

数据库与集合管理

javascript
// 查看所有数据库
show dbs

// 切换/创建数据库(插入数据后才真正创建)
use mydb

// 查看当前数据库
db

// 删除数据库
db.dropDatabase()

// 创建集合(显式创建,可指定选项)
db.createCollection("users", {
  capped: true,      // 固定大小集合
  size: 5242880,     // 最大 5MB
  max: 5000          // 最多 5000 个文档
})

// 查看集合
show collections

// 删除集合
db.users.drop()

CRUD 操作

创建(Create)

插入单个文档:

javascript
db.inventory.insertOne({
  item: "canvas",
  qty: 100,
  tags: ["cotton"],
  size: { h: 28, w: 35.5, uom: "cm" }
})
// 返回:{ acknowledged: true, insertedId: ObjectId("...") }

插入多个文档:

javascript
db.inventory.insertMany([
  { item: "journal", qty: 25, tags: ["blank", "red"] },
  { item: "mat", qty: 85, tags: ["gray"] }
])
// 返回:{ acknowledged: true, insertedCount: 2, insertedIds: {...} }

插入选项:

javascript
db.inventory.insertMany(
  [{ item: "book", qty: 10 }],
  { ordered: false }  // 遇到错误继续插入其他文档
)

读取(Read)

查询所有文档:

javascript
db.inventory.find({})

// 格式化输出
db.inventory.find({}).pretty()

条件查询:

javascript
// 等于
db.inventory.find({ item: "canvas" })

// 比较操作符
db.inventory.find({ qty: { $gte: 85 } })
db.inventory.find({ qty: { $gt: 20, $lt: 100 } })  // 范围查询

完整查询操作符:

操作符描述示例
$eq等于{ qty: { $eq: 25 } }
$ne不等于{ qty: { $ne: 25 } }
$gt/$gte大于/大于等于{ qty: { $gt: 25 } }
$lt/$lte小于/小于等于{ qty: { $lt: 25 } }
$in在数组中{ status: { $in: ["A", "D"] } }
$nin不在数组中{ status: { $nin: ["A", "D"] } }
$or或条件{ $or: [{ status: "A" }, { qty: { $lt: 30 } }] }
$and与条件{ $and: [{ qty: { $gt: 10 } }, { qty: { $lt: 50 } }] }
$not取反{ qty: { $not: { $gt: 25 } } }
$nor所有条件都不满足{ $nor: [{ price: 1.99 }, { sale: true }] }
$exists字段是否存在{ item: { $exists: true } }
$type字段类型{ item: { $type: "string" } }
$regex正则匹配{ name: { $regex: /^A/ } }
$mod取模运算{ qty: { $mod: [4, 0] } }

投影(只返回指定字段):

javascript
// 1 表示包含,0 表示排除
db.inventory.find(
  { status: "A" },
  { item: 1, status: 1, _id: 0 }
)

// 投影数组元素
db.inventory.find(
  { status: "A" },
  { item: 1, "tags.$": 1 }  // 只返回第一个匹配元素
)

// 使用 $slice 限制数组元素
db.inventory.find(
  { status: "A" },
  { comments: { $slice: 5 } }  // 只返回前 5 个评论
)

排序与分页:

javascript
// 排序:1 升序,-1 降序
db.inventory.find().sort({ qty: -1, item: 1 })

// 分页
db.inventory.find()
  .skip(10)   // 跳过前 10 条
  .limit(10)  // 返回 10 条

// 更高效的分页(使用 _id)
db.inventory.find({ _id: { $gt: lastId } }).limit(10)

计数:

javascript
// 计数文档数量
db.inventory.countDocuments({ status: "A" })

// 估算数量(更快但不精确)
db.inventory.estimatedDocumentCount()

更新(Update)

更新操作符完整列表:

操作符描述示例
$set设置字段值{ $set: { status: "active" } }
$unset移除字段{ $unset: { oldField: "" } }
$inc增加数值{ $inc: { qty: 5 } }
$mul乘以数值{ $mul: { price: 1.1 } }
$rename重命名字段{ $rename: { "oldName": "newName" } }
$setOnInsert仅插入时设置{ $setOnInsert: { createdAt: new Date() } }
$currentDate设置当前日期{ $currentDate: { updatedAt: true } }
$min取最小值{ $min: { lowScore: 50 } }
$max取最大值{ $max: { highScore: 100 } }

数组操作符:

操作符描述示例
$push添加元素{ $push: { tags: "new" } }
$pull移除匹配元素{ $pull: { tags: "old" } }
$pop移除首/尾元素{ $pop: { tags: 1 } }
$addToSet添加唯一元素{ $addToSet: { tags: "unique" } }
$pullAll移除多个元素{ $pullAll: { tags: ["a", "b"] } }

更新单个文档:

javascript
db.inventory.updateOne(
  { item: "journal" },
  {
    $set: { "size.uom": "in" },
    $inc: { qty: 5 },
    $currentDate: { updatedAt: true }
  }
)

更新多个文档:

javascript
db.inventory.updateMany(
  { qty: { $lt: 50 } },
  { $set: { status: "low" } }
)

替换文档:

javascript
// replaceOne 会替换整个文档(除 _id)
db.inventory.replaceOne(
  { item: "journal" },
  { item: "journal", qty: 100, status: "A" }
)

upsert(存在则更新,不存在则插入):

javascript
db.inventory.updateOne(
  { item: "book" },
  { $set: { qty: 20, status: "A" } },
  { upsert: true }
)

数组更新示例:

javascript
// 添加到数组
db.posts.updateOne(
  { _id: 1 },
  { $push: { comments: { user: "Alice", text: "Great!" } } }
)

// 添加多个元素
db.posts.updateOne(
  { _id: 1 },
  { $push: { tags: { $each: ["mongodb", "database", "nosql"] } } }
)

// 限制数组长度
db.posts.updateOne(
  { _id: 1 },
  {
    $push: {
      logs: {
        $each: ["log1", "log2"],
        $slice: -10  // 只保留最后 10 个
      }
    }
  }
)

// 更新数组中匹配的元素
db.inventory.updateOne(
  { _id: 1, "items.name": "pen" },
  { $set: { "items.$.qty": 50 } }  // $ 是占位符
)

// 更新所有匹配的数组元素
db.inventory.updateOne(
  { _id: 1 },
  { $set: { "items.$[].status": "available" } }
)

// 根据条件更新数组元素
db.inventory.updateOne(
  { _id: 1 },
  { $set: { "items.$[elem].status": "sold" } },
  { arrayFilters: [{ "elem.qty": { $lt: 10 } }] }
)

删除(Delete)

javascript
// 删除单个文档
db.inventory.deleteOne({ item: "paper" })

// 删除多个文档
db.inventory.deleteMany({ status: "low" })

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

// 返回:{ acknowledged: true, deletedCount: 5 }

高级查询

查询嵌套文档

javascript
// 精确匹配(字段顺序必须一致)
db.inventory.find({ size: { h: 14, w: 21, uom: "cm" } })

// 使用点符号查询嵌套字段
db.inventory.find({ "size.uom": "in" })
db.inventory.find({ "size.h": { $lt: 15 } })

查询数组

javascript
// 精确匹配整个数组(顺序必须一致)
db.inventory.find({ tags: ["red", "blank"] })

// 匹配顺序不同的数组
db.inventory.find({ tags: { $all: ["blank", "red"] } })

// 查询包含某元素
db.inventory.find({ tags: "red" })

// 查询包含多个元素(任意一个)
db.inventory.find({ tags: { $in: ["red", "blue"] } })

// 按数组长度查询
db.inventory.find({ tags: { $size: 3 } })

使用 $elemMatch

查询数组中至少有一个元素同时满足多个条件:

javascript
// instock 数组中存在 qty>10 且 qty<=20 的元素
db.inventory.find({
  instock: { $elemMatch: { qty: { $gt: 10, $lte: 20 } } }
})

// 多条件组合
db.inventory.find({
  instock: { $elemMatch: { qty: { $gt: 10 }, warehouse: "A" } }
})

查询空值与缺失字段

javascript
// 查询字段存在且值为 null
db.inventory.find({ field: null })

// 只查询字段存在(无论值是什么)
db.inventory.find({ field: { $exists: true } })

// 只查询字段不存在
db.inventory.find({ field: { $exists: false } })

// 查询字段存在且值为 null(类型检查)
db.inventory.find({ field: { $type: 10 } })

索引

索引是提升查询性能的关键工具。MongoDB 默认在 _id 字段创建唯一索引。

索引类型

类型描述创建示例
单字段索引单个字段的索引createIndex({ item: 1 })
复合索引多个字段组合createIndex({ item: 1, qty: -1 })
多键索引数组字段索引createIndex({ tags: 1 })
文本索引全文检索createIndex({ content: "text" })
地理空间索引位置查询createIndex({ location: "2dsphere" })
哈希索引哈希分片键createIndex({ _id: "hashed" })
通配符索引动态字段索引createIndex({ "attributes.$**": 1 })
TTL 索引自动过期删除createIndex({ createdAt: 1 }, { expireAfterSeconds: 3600 })

索引操作

javascript
// 创建索引(1 升序,-1 降序)
db.inventory.createIndex({ item: 1 })
db.inventory.createIndex({ item: 1, qty: -1 })

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

// 创建稀疏索引(只索引存在该字段的文档)
db.users.createIndex({ nickname: 1 }, { sparse: true })

// 创建部分索引(条件过滤)
db.orders.createIndex(
  { status: 1 },
  { partialFilterExpression: { status: "active" } }
)

// 创建文本索引
db.articles.createIndex({ title: "text", content: "text" })

// 创建 TTL 索引(1 小时后自动删除)
db.sessions.createIndex({ createdAt: 1 }, { expireAfterSeconds: 3600 })

// 查看索引
db.inventory.getIndexes()

// 查看索引大小
db.inventory.totalIndexSize()

// 删除索引
db.inventory.dropIndex("item_1")
db.inventory.dropIndexes()  // 删除所有索引(除 _id)

查询计划分析

javascript
// 基本分析
db.inventory.find({ item: "journal" }).explain()

// 详细执行统计
db.inventory.find({ item: "journal" }).explain("executionStats")

// 查看是否使用索引
// 关键字段:winningPlan.stage = "IXSCAN" 表示使用了索引
//          winningPlan.stage = "COLLSCAN" 表示全表扫描

索引策略

code
复合索引设计原则(ESR 规则):
1. Equality(等值匹配)优先
2. Sort(排序)次之
3. Range(范围查询)最后

示例查询:
db.orders.find({ status: "A" }).sort({ createdAt: -1 }).limit(10)

最佳索引:
db.orders.createIndex({ status: 1, createdAt: -1 })

索引使用建议:

  • 为高频查询字段创建索引
  • 复合索引字段顺序遵循 ESR 规则
  • 避免过多索引(影响写入性能)
  • 使用 explain() 验证索引使用情况
  • 定期使用 $indexStats 监控索引使用率
  • 考虑使用覆盖索引(Covered Query)

聚合框架

聚合管道用于数据处理和分析,数据依次通过各个阶段进行处理。

聚合管道流程图

code
输入文档 → $match → $group → $sort → $project → 输出结果
              ↓         ↓        ↓         ↓
           筛选      分组      排序     重塑

常用聚合阶段

阶段描述示例
$match筛选文档{ $match: { status: "A" } }
$group分组并计算{ $group: { _id: "$dept", total: { $sum: 1 } } }
$project重塑文档结构{ $project: { name: 1, total: 1 } }
$sort排序{ $sort: { total: -1 } }
$limit限制输出数量{ $limit: 10 }
$skip跳过指定数量{ $skip: 10 }
$unwind展开数组{ $unwind: "$tags" }
$lookup关联查询见下文示例
$addFields添加新字段{ $addFields: { total: { $add: ["$a", "$b"] } } }
$replaceRoot替换根文档{ $replaceRoot: { newRoot: "$details" } }
$count计数{ $count: "total" }
$facet多管道并行处理见下文示例
$bucket分桶统计见下文示例

聚合累加器

累加器描述示例
$sum求和{ $sum: "$amount" }
$avg平均值{ $avg: "$score" }
$min/$max最小/最大值{ $min: "$price" }
$first/$last第一个/最后一个{ $first: "$name" }
$push添加到数组{ $push: "$name" }
$addToSet添加唯一值到数组{ $addToSet: "$category" }

聚合示例

基础分组统计:

javascript
db.orders.aggregate([
  // 阶段1:筛选
  { $match: { status: "A" } },
  // 阶段2:分组统计
  {
    $group: {
      _id: "$cust_id",
      totalAmount: { $sum: "$amount" },
      avgAmount: { $avg: "$amount" },
      orderCount: { $sum: 1 }
    }
  },
  // 阶段3:排序
  { $sort: { totalAmount: -1 } },
  // 阶段4:限制
  { $limit: 10 }
])

关联查询($lookup):

javascript
// 类似 SQL 的 LEFT JOIN
db.orders.aggregate([
  {
    $lookup: {
      from: "users",           // 关联的集合
      localField: "userId",    // 本地字段
      foreignField: "_id",     // 外键字段
      as: "userInfo"           // 输出字段名
    }
  },
  // userInfo 是数组,通常需要展开
  {
    $project: {
      orderId: "$_id",
      userId: 1,
      userName: { $arrayElemAt: ["$userInfo.name", 0] }
    }
  }
])

// MongoDB 3.6+ 简化语法
db.orders.aggregate([
  {
    $lookup: {
      from: "users",
      let: { userId: "$userId" },
      pipeline: [
        { $match: { $expr: { $eq: ["$_id", "$$userId"] } } },
        { $project: { name: 1, email: 1 } }
      ],
      as: "userInfo"
    }
  }
])

展开数组($unwind):

javascript
db.inventory.aggregate([
  { $unwind: "$tags" },
  { $group: { _id: "$tags", count: { $sum: 1 } } },
  { $sort: { count: -1 } }
])

// 保留空数组文档
db.inventory.aggregate([
  { $unwind: { path: "$tags", preserveNullAndEmptyArrays: true } }
])

条件逻辑($cond, $switch):

javascript
db.sales.aggregate([
  {
    $project: {
      item: 1,
      discount: {
        $cond: {
          if: { $gte: ["$qty", 100] },
          then: 0.1,
          else: 0
        }
      }
    }
  }
])

// 复杂条件
db.sales.aggregate([
  {
    $addFields: {
      grade: {
        $switch: {
          branches: [
            { case: { $gte: ["$score", 90] }, then: "A" },
            { case: { $gte: ["$score", 80] }, then: "B" },
            { case: { $gte: ["$score", 70] }, then: "C" }
          ],
          default: "F"
        }
      }
    }
  }
])

多管道并行处理($facet):

javascript
db.products.aggregate([
  {
    $facet: {
      byCategory: [
        { $group: { _id: "$category", count: { $sum: 1 } } }
      ],
      byPriceRange: [
        { $bucket: {
            groupBy: "$price",
            boundaries: [0, 50, 100, 200, 500],
            default: "other",
            output: { count: { $sum: 1 }, avgPrice: { $avg: "$price" } }
        }}
      ],
      totalStats: [
        { $group: { _id: null, total: { $sum: 1 }, avgPrice: { $avg: "$price" } } }
      ]
    }
  }
])

上一篇概述与安装
下一篇:[Node.js 操作 MongoDB](3-Node.js 操作.md)