01. MongoDB 核心概念与 CRUD 操作

MongoDB 基础入门: BSON 数据模型、文档结构、CRUD 操作与批量写入

MongoDB 是一个面向文档的 NoSQL 数据库,使用 BSON(Binary JSON)作为数据格式。相比传统关系型数据库的表-行结构,MongoDB 采用集合-文档的层次结构,更适合存储半结构化数据和快速迭代场景。它以水平扩展能力强、查询语言丰富、社区生态活跃而闻名,广泛应用于电商平台、物联网、实时分析以及内容管理等领域。

在生产环境中,MongoDB 通常以副本集(Replica Set)或分片集群(Sharding Cluster)的形式部署,既可以通过本地安装快速启动,也可以直接选用 MongoDB Atlas 云托管服务实现零运维上线。理解其文档模型和核心操作是进阶使用索引、聚合、事务以及性能优化的前提。

1. 核心概念对比

关系型数据库MongoDB说明
DatabaseDatabase逻辑数据库
TableCollection集合(无固定 schema)
RowDocumentBSON 文档(最大 16MB)
ColumnField字段
IndexIndex索引
Primary Key_id默认 ObjectId
Join$lookup / 嵌入聚合管道或文档内嵌

MongoDB 不强制集合内的文档具有统一结构,这种灵活性允许在同一集合中存储不同字段组合的文档。但自由并不意味着无序,合理的 schema 设计仍然是保障查询性能和数据一致性的关键。

2. BSON 数据类型

BSON 是 MongoDB 的数据存储格式,在 JSON 的基础上扩展了二进制安全、类型丰富以及遍历高效等特性。与纯文本 JSON 相比,BSON 对数字和日期等类型有更精确的表达,避免了 JSON 中 “整数 / 浮点 / 大数” 类型模糊的问题。

{
    _id: ObjectId("64a1b2c3d4e5f6a7b8c9d0e1"),  // 12字节唯一ID
    name: "Alice",                                   // String
    age: 30,                                         // Int32
    salary: 12500.50,                                // Double
    isActive: true,                                  // Boolean
    tags: ["developer", "admin"],                    // Array
    address: {                                       // Embedded Document
        city: "Beijing",
        zip: "100000"
    },
    createdAt: ISODate("2024-01-15T08:00:00Z"),     // Date
    metadata: null,                                  // Null
    profile: BinData(0, "base64string"),             // Binary
    config: undefined                                // Undefined (避免使用)
}

ObjectId 结构:

64a1b2c3 d4e5f6 a7b8c9    d0e1
 timestamp  machine pid   counter
   (4B)    (3B)   (2B)    (3B)

ObjectId 的生成规则天然具备大致有序性,常被用于默认主键和按时间排序的查询替代方案。需要注意的是,由于包含机器标识和随机种子,ObjectId 在分布式环境中具备全局唯一性,不应该依赖其精确时间戳做跨毫秒的排序。

3. 数据库与集合操作

// 创建/切换数据库
use ecommerce;

// 查看当前数据库中的集合列表
db.getCollectionNames();

// 创建集合(带选项)
db.createCollection("products", {
    capped: true,           // 固定大小集合,类似循环日志
    size: 5242880,          // 5MB
    max: 5000               // 最多5000条文档
});

// 删除集合(不可逆,慎用)
db.products.drop();

// 查看集合统计信息,包括文档数、索引大小、存储大小等
db.products.stats();

固定大小集合(Capped Collection)适用于日志流和消息队列场景,写入速度极快且自动覆盖旧数据,但无法单独删除其中某条文档,也不能更新导致文档体积增大。

4. CRUD 操作

4.1 Create(插入)

// 插入单条文档
db.users.insertOne({
    name: "Alice",
    email: "alice@example.com",
    age: 30,
    createdAt: new Date()
});

// 插入多条(无序,遇到错误继续)
db.users.insertMany([
    { name: "Bob", age: 25, tags: ["new"] },
    { name: "Charlie", age: 35, tags: ["vip"] }
], { ordered: false });

// 原子递增插入,常用于自增 ID 生成器
db.counters.findOneAndUpdate(
    { _id: "orderId" },
    { $inc: { seq: 1 } },
    { upsert: true, returnDocument: "after" }
);

无序插入在批量导入时效率更高,因为单条失败不会中断整个批次。insertOne 和 insertMany 都会返回 acknowledged、insertedId 等字段,便于业务层确认写入状态。

4.2 Read(查询)

// 精确匹配
db.users.find({ age: 30 });

// 比较操作符
db.products.find({
    price: { $gte: 100, $lte: 500 },
    stock: { $gt: 0 }
});

// 逻辑组合
db.users.find({
    $and: [
        { age: { $gte: 18 } },
        { $or: [
            { status: "active" },
            { vip: true }
        ]}
    ]
});

// 数组查询
db.users.find({ tags: { $in: ["developer", "admin"] } });        // 包含任一
db.users.find({ tags: { $all: ["developer", "mongodb"] } });     // 包含全部
db.users.find({ tags: { $size: 2 } });                            // 数组长度

// 元素级匹配(数组中的对象)
db.orders.find({ items: { $elemMatch: { sku: "SKU001", qty: { $gt: 5 } } } });

// 投影(只返回指定字段,减少网络传输)
db.users.find({}, { name: 1, email: 1, _id: 0 });

// 排序+分页
db.users.find()
    .sort({ createdAt: -1 })    // 降序
    .skip(20)                    // 跳过前20条
    .limit(10);                  // 取10条

比较操作符速查:

操作符含义
$eq等于
$ne不等于
$gt / $gte大于 / 大于等于
$lt / $lte小于 / 小于等于
$in / $nin在 / 不在数组中
$regex正则匹配
$exists字段是否存在
$type字段类型匹配

对于高并发系统,skip 跳过大量文档成本较高,建议改用范围查询(如基于 _id 或时间戳的游标分页)来提升性能。

4.3 Update(更新)

// 更新单条
db.users.updateOne(
    { name: "Alice" },
    { $set: { age: 31, updatedAt: new Date() } }
);

// 更新多条
db.users.updateMany(
    { status: "pending" },
    { $set: { status: "active" }, $inc: { loginCount: 1 } }
);

// 更新操作符速查
{ $set: { field: value } }           // 设置字段
{ $unset: { field: "" } }            // 删除字段
{ $inc: { counter: 1 } }             // 递增
{ $mul: { price: 1.1 } }             // 相乘
{ $push: { tags: "new-tag" } }       // 数组尾部添加
{ $addToSet: { tags: "new-tag" } }   // 数组添加(去重)
{ $pull: { tags: "old-tag" } }       // 数组移除
{ $pop: { tags: 1 } }                // 数组弹出尾部(-1为头部)
{ $rename: { oldName: "newName" } }  // 重命名字段

在更新操作中,$set 只会覆盖指定字段而不会替换整个文档,这是 MongoDB 更新语义与覆盖写入的本质区别。$inc 和 $mul 在并发更新数字字段时可以避免读取-修改-写入的竞争问题。

4.4 Delete(删除)

// 删除单条
db.users.deleteOne({ _id: ObjectId("64a1b2c3d4e5f6a7b8c9d0e1") });

// 删除多条
db.logs.deleteMany({ createdAt: { $lt: new Date(Date.now() - 30*24*60*60*1000) } });

// 清空集合(比逐条 deleteMany 更快,但不会回收磁盘空间)
db.users.deleteMany({});

// 删除集合并回收空间
db.users.drop();

执行删除前务必先运行 find 确认目标文档,避免误删。对于历史数据清理,可以考虑使用 TTL 索引,让 MongoDB 自动按时间删除过期文档。

5. 批量操作与优化

// 有序批量写入(默认): 遇到错误停止
db.users.bulkWrite([
    { insertOne: { document: { name: "User1" } } },
    { updateOne: { filter: { name: "Alice" }, update: { $set: { age: 32 } } } },
    { deleteOne: { filter: { name: "Bob" } } }
]);

// 游标遍历(大数据量避免内存溢出)
const cursor = db.largeCollection.find().batchSize(1000);
cursor.forEach(doc => {
    process(doc);
});

// explain 分析查询性能
db.users.find({ email: "alice@example.com" }).explain("executionStats");

explain("executionStats") 是排查慢查询的核心手段。输出中的 executionTimeMillis、totalDocsExamined 和 totalKeysExamined 能直观反映索引是否命中。如果 totalDocsExamined 远大于返回文档数,说明查询正在做全表扫描,亟待添加索引。

6. 文档模型设计原则

MongoDB 的灵活性并不意味着可以随意嵌套。好的文档设计需要在查询性能、更新便利性和数据冗余之间取得平衡。

嵌入 vs 引用

嵌入适合一对少量、一起读写、不会持续增长的数据;引用适合一对多且多的一方会持续增长、需要独立更新的数据。

// 嵌入示例:订单内嵌商品条目(一起读写,条目数量有限)
db.orders.insertOne({
    _id: ObjectId(),
    customerId: "C001",
    items: [
        { sku: "SKU001", name: "T-Shirt", qty: 2, price: 99.0 },
        { sku: "SKU002", name: "Sneakers", qty: 1, price: 599.0 }
    ],
    total: 797.0,
    createdAt: new Date()
});

// 引用示例:博客文章引用作者(作者信息独立维护,多篇文章复用)
db.authors.insertOne({ _id: ObjectId(), name: "Leeting", bio: "Backend Engineer" });
db.posts.insertOne({
    title: "MongoDB 入门",
    authorId: ObjectId("..."),  // 引用 authors 集合
    content: "...",
    createdAt: new Date()
});

Schema 验证

从 MongoDB 3.6 开始,集合支持 JSON Schema 验证规则,可以强制字段类型、边界和必填项。

db.createCollection("users", {
    validator: {
        $jsonSchema: {
            bsonType: "object",
            required: ["name", "email", "age"],
            properties: {
                name: { bsonType: "string", description: "must be a string" },
                email: { bsonType: "string", pattern: "^.+@.+$" },
                age: { bsonType: "int", minimum: 0, maximum: 150 }
            }
        }
    },
    validationLevel: "strict",
    validationAction: "error"
});

validationLevel: "moderate" 则仅对新插入和更新的文档生效,不影响存量数据。Schema 验证是防止脏数据进入数据库的最后一道防线。

BSON 设计注意事项

  • 单个文档上限 16MB,避免无节制地内嵌数组或大型二进制对象。
  • 字段名在每条文档中重复存储,命名应尽量简短。
  • 避免在文档中存储冗长的空值字段,这会增加存储和传输开销。

7. CRUD 操作进阶

BulkWrite

bulkWrite 支持在一次网络往返中混合执行插入、更新、替换和删除,适合数据同步和批量迁移场景。

db.products.bulkWrite([
    { insertOne: { document: { sku: "SKU003", name: "Hat", price: 49.9 } } },
    { updateMany: { filter: { category: "clothing" }, update: { $inc: { price: 5 } } } },
    { replaceOne: { filter: { sku: "SKU001" }, replacement: { sku: "SKU001", name: "T-Shirt V2", price: 129.0 } } },
    { deleteMany: { filter: { stock: { $lte: 0 } } } }
], { ordered: false });

FindAndModify

findAndModify 是原子读写操作,适用于秒杀扣减库存、领取优惠券等需要"查后改"且必须串行化的场景。

// 原子扣减库存并返回更新后的文档
db.inventory.findOneAndUpdate(
    { sku: "SKU001", stock: { $gte: 1 } },
    { $inc: { stock: -1 } },
    { returnDocument: "after", upsert: false }
);

Upsert

当查询条件匹配不到文档时,upsert: true 会自动插入新文档,常用于如果不存在则创建、存在则更新的幂等写入。

db.configs.updateOne(
    { key: "maintenance_mode" },
    { $set: { value: true, updatedAt: new Date() } },
    { upsert: true }
);

8. 索引基础

索引是 MongoDB 查询性能的生命线。没有索引的查询会触发全集合扫描(COLLSCAN),在数据量增大时性能迅速恶化。

单字段索引

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

// 为 createdAt 创建降序索引
db.logs.createIndex({ createdAt: -1 });

复合索引

复合索引的字段顺序至关重要。最左前缀原则决定了索引能否被查询利用。

// 复合索引:先按 status 筛选,再按 createdAt 排序
db.orders.createIndex({ status: 1, createdAt: -1 });

// 该索引支持的查询
db.orders.find({ status: "shipped" }).sort({ createdAt: -1 });
db.orders.find({ status: "shipped", createdAt: { $gte: new Date("2024-01-01") } });

文本索引

文本索引用于全文检索,支持按关键词搜索和语言分词。

db.articles.createIndex({ title: "text", content: "text" });

// 搜索包含 MongoDB 或索引的文章
db.articles.find({ $text: { $search: "MongoDB indexing" } });

// 返回相关性得分并按得分排序
db.articles.find(
    { $text: { $search: "database" } },
    { score: { $meta: "textScore" } }
).sort({ score: { $meta: "textScore" } });

地理空间索引

地理空间索引支持基于坐标的位置查询,广泛应用于配送范围查询和附近推荐。

// 创建 2dsphere 索引
db.stores.createIndex({ location: "2dsphere" });

// 插入带坐标的文档
db.stores.insertOne({
    name: "Beijing Store",
    location: { type: "Point", coordinates: [116.4074, 39.9042] }
});

// 查询距离北京天安门 5000 米内的门店
db.stores.find({
    location: {
        $near: {
            $geometry: { type: "Point", coordinates: [116.4074, 39.9042] },
            $maxDistance: 5000
        }
    }
});

9. 聚合框架入门

聚合框架是 MongoDB 的数据处理管道,通过将多个阶段(Stage)串联起来,实现过滤、分组、排序、关联和重塑等复杂计算,无需将数据拉取到应用层。

// 按月份统计订单金额和数量
db.orders.aggregate([
    { $match: { status: "completed" } },
    { $group: {
        _id: { $dateToString: { format: "%Y-%m", date: "$createdAt" } },
        totalAmount: { $sum: "$amount" },
        orderCount: { $sum: 1 }
    }},
    { $sort: { _id: 1 } },
    { $limit: 12 }
]);

常用聚合阶段一览:

阶段作用
$match过滤文档,类似 find 的条件
$group分组统计,必须使用 _id 指定分组键
$sort排序,内存限制 100MB,大数据量建议先 $match
$project投影 / 重塑字段,控制输出结构
$limit / $skip分页截取
$lookup左外连接,关联其他集合
$unwind拆分数组,每元素生成一行
$addFields新增计算字段

关联查询示例

db.orders.aggregate([
    { $match: { status: "completed" } },
    { $lookup: {
        from: "users",
        localField: "userId",
        foreignField: "_id",
        as: "userInfo"
    }},
    { $unwind: "$userInfo" },
    { $project: {
        orderId: "$_id",
        total: 1,
        userName: "$userInfo.name",
        userEmail: "$userInfo.email"
    }}
]);

聚合管道在分片集群上会自动分发到各个分片执行,结果汇总后再进入后续阶段,具备良好的水平扩展能力。对于超大规模数据聚合,可以结合 $merge 将结果输出到目标集合,避免内存瓶颈。

10. 与主流语言集成

MongoDB 官方为各大主流语言提供了原生驱动,支持连接池、负载均衡、读写分离和事务等高级特性。

Node.js

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

async function main() {
    const client = new MongoClient('mongodb://localhost:27017');
    await client.connect();
    const db = client.db('ecommerce');
    const users = db.collection('users');

    await users.insertOne({ name: 'Alice', age: 30 });
    const result = await users.find({ age: { $gte: 25 } }).toArray();
    console.log(result);
    await client.close();
}

main();

Java

import com.mongodb.client.*;
import org.bson.Document;

public class MongoExample {
    public static void main(String[] args) {
        try (MongoClient client = MongoClients.create("mongodb://localhost:27017")) {
            MongoDatabase db = client.getDatabase("ecommerce");
            MongoCollection<Document> users = db.getCollection("users");

            users.insertOne(new Document("name", "Alice").append("age", 30));
            users.find(new Document("age", new Document("$gte", 25)))
                 .forEach(doc -> System.out.println(doc.toJson()));
        }
    }
}

Python

from pymongo import MongoClient

client = MongoClient("mongodb://localhost:27017")
db = client["ecommerce"]
users = db["users"]

users.insert_one({"name": "Alice", "age": 30})
for doc in users.find({"age": {"$gte": 25}}):
    print(doc)

实际项目中,建议配合使用 ODM / ORM 框架,如 Node.js 的 Mongoose、Java 的 Spring Data MongoDB、Python 的 MongoEngine,以获得类型安全、Schema 校验和更优雅的业务层抽象。

11. Atlas 云托管入门与免费集群

MongoDB Atlas 是 MongoDB 官方提供的全托管数据库即服务(DBaaS),支持 AWS、Azure 和 Google Cloud 三大云平台。开发者无需关注服务器选型、副本集配置、备份和版本升级,几分钟内即可创建可生产的集群。

免费集群创建步骤

  1. 访问 https://cloud.mongodb.com 注册账号。
  2. 创建新 Project,选择 Shared Cluster(M0 免费级)。
  3. 选择最接近用户的云区域,如 AWS ap-east-1(香港)或 cn-north-1(如可用)。
  4. 配置数据库访问身份验证:创建用户名和密码。
  5. 配置网络访问白名单,建议先添加 0.0.0.0/0 方便开发测试,生产环境替换为固定出口 IP。
  6. 点击 Connect,选择驱动程序类型,复制连接字符串。

连接字符串示例:

mongodb+srv://<user>:<password>@cluster0.xxxxx.mongodb.net/mydb?retryWrites=true&w=majority

Atlas 免费层提供 512MB 存储空间、共享 vCPU 和三节点副本集,足够支持个人项目、原型验证和小型生产应用。付费层支持自动扩展、全局集群、网络隔离(VPC Peering / PrivateLink)以及高级监控告警。

常用 Atlas 功能

  • Real-Time Performance Panel:实时查看慢查询和锁竞争。
  • Data Explorer:Web 界面直接浏览和修改文档。
  • Triggers:基于数据库变更事件触发 Serverless 函数(类似 AWS Lambda)。
  • Data Lake:将 Atlas 数据与 AWS S3 / Azure Blob 打通,统一分析查询。

延伸阅读

继续阅读

探索更多技术文章

浏览归档,发现更多关于系统设计、工具链和工程实践的内容。

全部文章 返回首页

「mongodb」更多文章

  1. 数据生命周期、TTL 与冷热归档
  2. $graphLookup 与层次结构建模
  3. GridFS 与大文件存储实践