MongoDB(45) 嵌入式文档与引用的优缺点是什么?

嵌入式文档(Embedded Documents)和引用(References)是MongoDB中常用的两种数据建模方式,它们各有优缺点。选择使用哪种方式取决于具体的应用场景和需求。

嵌入式文档

优点

  1. 读取效率高:嵌入式文档通常可以在一次查询中获取所有相关数据,减少了数据库查询次数。
  2. 事务一致性:由于所有相关数据都在同一个文档中,更新时可以保证数据的一致性,不需要担心分布式事务问题。
  3. 简单性:数据模型简单,查询和处理方便。

缺点

  1. 文档大小限制:MongoDB文档大小限制为16MB,嵌入式文档如果过大,可能会超过这个限制。
  2. 冗余数据:在一些场景下,嵌入式文档会导致数据冗余,增加存储空间。
  3. 更新效率低:如果嵌入的数据结构复杂,更新可能会变得比较繁琐和低效。

引用

优点

  1. 数据去重:引用方式将数据分离到不同的集合中,减少了数据冗余。
  2. 灵活性高:各个集合独立更新,灵活性更高,适合复杂的数据模型。
  3. 文档大小分离:因为数据被分离到不同的文档中,不会受到单个文档大小的限制。

缺点

  1. 查询效率低:需要多次查询才能获取所有相关数据,特别是在大数据量下查询效率可能较低。
  2. 事务一致性差:跨集合的更新操作不能保证原子性,需要额外处理事务一致性问题。
  3. 复杂性高:数据模型复杂,需要处理数据的关联和联合查询。

使用示例

以下示例展示了在Node.js中如何使用嵌入式文档和引用,并对比它们的优缺点。

安装MongoDB的Node.js驱动

bash 复制代码
npm install mongodb

嵌入式文档示例

插入数据
javascript 复制代码
const { MongoClient } = require('mongodb');

async function insertEmbeddedData() {
    const uri = "mongodb://localhost:27017";
    const client = new MongoClient(uri, { useUnifiedTopology: true });

    try {
        await client.connect();
        const db = client.db('myDatabase');
        const usersCollection = db.collection('users');

        await usersCollection.deleteMany({}); // 清空集合

        await usersCollection.insertMany([
            {
                userId: 1,
                name: "Alice",
                address: {
                    street: "123 Main St",
                    city: "New York",
                    state: "NY",
                    zip: "10001"
                },
                orders: [
                    { orderId: 101, amount: 50.5, date: new Date("2022-01-01") },
                    { orderId: 102, amount: 100.0, date: new Date("2022-02-01") }
                ]
            },
            {
                userId: 2,
                name: "Bob",
                address: {
                    street: "456 Maple Ave",
                    city: "San Francisco",
                    state: "CA",
                    zip: "94101"
                },
                orders: [
                    { orderId: 103, amount: 75.0, date: new Date("2022-01-15") },
                    { orderId: 104, amount: 200.0, date: new Date("2022-03-01") }
                ]
            }
        ]);

        console.log("Embedded data inserted");
    } finally {
        await client.close();
    }
}

insertEmbeddedData().catch(console.error);
查询数据
javascript 复制代码
async function queryEmbeddedData() {
    const uri = "mongodb://localhost:27017";
    const client = new MongoClient(uri, { useUnifiedTopology: true });

    try {
        await client.connect();
        const db = client.db('myDatabase');
        const usersCollection = db.collection('users');

        // 查询某个用户及其所有订单
        console.log("\nQuery a user and their orders:");
        let user = await usersCollection.findOne({ name: "Alice" });
        console.log(user);

    } finally {
        await client.close();
    }
}

queryEmbeddedData().catch(console.error);

嵌入式文档的查询效率很高,因为所有数据都在同一个文档中。

引用示例

插入数据
javascript 复制代码
const { MongoClient, ObjectId } = require('mongodb');

async function insertReferencedData() {
    const uri = "mongodb://localhost:27017";
    const client = new MongoClient(uri, { useUnifiedTopology: true });

    try {
        await client.connect();
        const db = client.db('myDatabase');
        const usersCollection = db.collection('users');
        const ordersCollection = db.collection('orders');

        await usersCollection.deleteMany({}); // 清空用户集合
        await ordersCollection.deleteMany({}); // 清空订单集合

        // 插入用户数据
        const users = await usersCollection.insertMany([
            { name: "Alice" },
            { name: "Bob" }
        ]);

        // 插入订单数据,并使用用户的ObjectId作为引用
        await ordersCollection.insertMany([
            { userId: users.insertedIds[0], amount: 50.5, date: new Date("2022-01-01") },
            { userId: users.insertedIds[0], amount: 100.0, date: new Date("2022-02-01") },
            { userId: users.insertedIds[1], amount: 75.0, date: new Date("2022-01-15") },
            { userId: users.insertedIds[1], amount: 200.0, date: new Date("2022-03-01") }
        ]);

        console.log("Referenced data inserted");
    } finally {
        await client.close();
    }
}

insertReferencedData().catch(console.error);
查询数据
javascript 复制代码
async function queryReferencedData() {
    const uri = "mongodb://localhost:27017";
    const client = new MongoClient(uri, { useUnifiedTopology: true });

    try {
        await client.connect();
        const db = client.db('myDatabase');
        const usersCollection = db.collection('users');
        const ordersCollection = db.collection('orders');

        // 查询某个用户及其所有订单
        console.log("\nQuery a user and their orders:");
        let user = await usersCollection.findOne({ name: "Alice" });
        let orders = await ordersCollection.find({ userId: user._id }).toArray();
        console.log({ user, orders });

        // 查询所有订单及其对应的用户信息
        console.log("\nQuery all orders and their corresponding users:");
        let results = await ordersCollection.aggregate([
            {
                $lookup: {
                    from: 'users',  // 要关联的集合
                    localField: 'userId',  // orders 集合中的字段
                    foreignField: '_id',  // users 集合中的字段
                    as: 'user'  // 输出数组字段
                }
            },
            {
                $unwind: '$user'  // 展开数组
            }
        ]).toArray();
        console.log(results);

    } finally {
        await client.close();
    }
}

queryReferencedData().catch(console.error);

通过这些示例,我们可以看到两种数据建模方式在操作上的不同:

  1. 嵌入式文档适用于小型数据集和简单的数据关系,读取效率高,数据一致性好,但是在数据量大或者关系复杂的情况下会导致文档过大,更新效率低。

  2. 引用适用于大型数据集和复杂的数据关系,数据去重和独立更新灵活,但查询效率低,特别是在需要多次查询获取相关数据时。此外,跨集合的事务一致性需要额外处理。

选择合适的数据建模方式取决于具体的应用场景和需求。对于一些应用,可以混合使用这两种方式,结合它们的优点,达到最佳的性能和灵活性。

相关推荐
JaguarJack2 小时前
告别阻塞!用 PHP TrueAsync 实现 PHP 脚本提速 10 倍
后端·php
Victor3562 小时前
MongoDB(44)什么是引用?
后端
无限大611 小时前
《AI观,观AI》:善用AI赋能|让AI成为你深耕核心、推进重心的“最强助手”
前端·后端
uzong11 小时前
CoPaw是什么?-- 2026年开源的国产个人AI助手
人工智能·后端
无心水11 小时前
【任务调度:框架】11、分布式任务调度进阶:高可用、幂等性、性能优化三板斧
人工智能·分布式·后端·性能优化·架构·2025博客之星·分布式调度框架
pjw1988090311 小时前
Spring Framework 中文官方文档
java·后端·spring
盒马盒马11 小时前
Rust:迭代器
开发语言·后端·rust
( •̀∀•́ )92012 小时前
Spring Boot 启动报错 `BindException: Permission denied`
java·spring boot·后端
渔阳节度使13 小时前
SpringAI实时监控+观测性
后端·python·flask