MK
摩柯社区 - 一个极简的技术知识社区
AI 面试

MongoDB插入文档实战指南

2021-11-095.2k 阅读

一、MongoDB 插入文档基础概述

MongoDB 是一种 NoSQL 数据库,以文档(document)的形式存储数据。文档是一种灵活的数据结构,类似 JSON 对象。在 MongoDB 中插入文档是最基本的操作之一,它允许我们向集合(collection)中添加数据。集合类似于关系型数据库中的表,但它不需要预先定义模式(schema),这使得数据插入变得更加灵活。

1.1 文档结构与数据类型

在深入插入操作之前,我们需要了解 MongoDB 中文档的结构和支持的数据类型。

文档是由字段(field)和值(value)对组成的。例如,以下是一个简单的文档示例,代表一个用户信息:

{
    "name": "John Doe",
    "age": 30,
    "email": "johndoe@example.com"
}

MongoDB 支持多种数据类型,常见的包括:

  • 字符串(String):用于存储文本数据,如上述示例中的 nameemail 字段。
  • 数值(Number):可以是整数或浮点数,像 age 字段。
  • 布尔值(Boolean)truefalse,用于表示逻辑状态。
  • 日期(Date):存储日期和时间信息,通过 new Date() 创建。
  • 数组(Array):可以存储多个值的有序列表。例如:
{
    "name": "Jane Smith",
    "hobbies": ["reading", "painting", "swimming"]
}
  • 内嵌文档(Embedded Document):文档中可以嵌套其他文档。比如:
{
    "name": "Bob Johnson",
    "address": {
        "street": "123 Main St",
        "city": "Anytown",
        "zip": "12345"
    }
}

1.2 集合的概念

集合是 MongoDB 中存储文档的容器。与关系型数据库中的表不同,集合不需要预先定义结构。当你插入第一个文档时,集合会自动创建。例如,如果你要存储用户信息,你可以使用名为 users 的集合:

// 在 MongoDB shell 中插入第一个文档到 users 集合
db.users.insertOne({
    "name": "Alice",
    "age": 25
})

这行代码会在 users 集合不存在时创建它,并插入一个新的用户文档。

二、使用 insertOne 方法插入单个文档

insertOne 方法是 MongoDB 中用于插入单个文档的基本方法。它接受一个文档作为参数,并将其插入到指定的集合中。

2.1 基本语法

在 MongoDB shell 中,insertOne 的基本语法如下:

db.collection.insertOne(
   <document>,
   {
     writeConcern: <document>
   }
)

其中,<document> 是要插入的文档对象,writeConcern 是可选参数,用于指定写入操作的确认级别。如果不指定 writeConcern,将使用默认的写入关注点。

2.2 示例代码

假设我们有一个 products 集合,用于存储商品信息。以下是插入单个商品文档的示例:

// 插入一个商品文档
db.products.insertOne({
    "name": "Laptop",
    "description": "A high - performance laptop",
    "price": 1200,
    "category": "Electronics"
})

执行上述代码后,products 集合中会插入一个新的商品文档。如果插入成功,insertOne 方法会返回一个包含插入结果的对象,其中 insertedId 字段是插入文档的唯一标识符:

{
    "acknowledged": true,
    "insertedId": ObjectId("60e9f9d87c1e7c2e6a16e6f5")
}

acknowledged 字段表示写入操作是否被服务器确认。如果 acknowledgedtrue,表示写入成功,并且 insertedId 字段包含新插入文档的 _id

2.3 处理重复键错误

在 MongoDB 中,默认情况下,集合中的每个文档都有一个唯一的 _id 字段。如果尝试插入一个 _id 已经存在的文档,会抛出重复键错误。例如:

// 第一次插入
db.test.insertOne({ "_id": 1, "name": "Item 1" })
// 第二次插入,尝试插入相同 _id 的文档
db.test.insertOne({ "_id": 1, "name": "Item 2" })

第二次插入会抛出以下错误:

WriteError({
    "index": 0,
    "code": 11000,
    "errmsg": "E11000 duplicate key error collection: test.test index: _id_ dup key: { : 1.0 }",
    "op": {
        "_id": 1,
        "name": "Item 2"
    }
})

为了避免这种错误,你可以确保插入的文档具有唯一的 _id,或者在需要时使用更新操作而不是插入操作。

三、使用 insertMany 方法插入多个文档

insertMany 方法允许一次性插入多个文档到集合中。这在批量插入数据时非常有用,可以减少与数据库的交互次数,提高插入效率。

3.1 基本语法

在 MongoDB shell 中,insertMany 的基本语法如下:

db.collection.insertMany(
   [ <document 1>, <document 2>,... ],
   {
     writeConcern: <document>,
     ordered: <boolean>
   }
)

<document 1>, <document 2>,... 是要插入的文档数组。writeConcern 同样是可选的写入关注点参数。ordered 是一个布尔值,默认为 true。如果 orderedtrue,插入操作将按顺序执行,一旦遇到错误,插入操作将停止;如果 orderedfalse,插入操作将并行执行,所有文档都会尝试插入,即使某些文档插入失败。

3.2 示例代码

假设我们要向 students 集合中插入多个学生文档。以下是示例代码:

// 定义要插入的学生文档数组
const students = [
    { "name": "Tom", "age": 18, "grade": "A" },
    { "name": "Jerry", "age": 19, "grade": "B" },
    { "name": "Mike", "age": 20, "grade": "C" }
];

// 使用 insertMany 插入多个学生文档
db.students.insertMany(students)

如果插入成功,insertMany 方法会返回一个包含插入结果的对象,其中 insertedIds 字段是一个数组,包含每个插入文档的 _id

{
    "acknowledged": true,
    "insertedIds": [
        ObjectId("60e9fa957c1e7c2e6a16e6f6"),
        ObjectId("60e9fa957c1e7c2e6a16e6f7"),
        ObjectId("60e9fa957c1e7c2e6a16e6f8")
    ]
}

3.3 处理部分插入失败

orderedtrue 时,如果插入过程中遇到错误,插入操作将停止。例如,假设我们有一个 employees 集合,并且已经定义了一个唯一索引 email。如果尝试插入两个具有相同 email 的文档:

// 定义要插入的员工文档数组
const employees = [
    { "name": "Alice", "email": "alice@example.com" },
    { "name": "Bob", "email": "alice@example.com" }
];

// 使用 insertMany 插入多个员工文档,ordered 为 true(默认)
db.employees.insertMany(employees)

由于第二个文档的 email 与第一个文档重复,会抛出错误,并且只有第一个文档会被插入:

WriteError({
    "index": 1,
    "code": 11000,
    "errmsg": "E11000 duplicate key error collection: test.employees index: email_1 dup key: { : \"alice@example.com\" }",
    "op": {
        "name": "Bob",
        "email": "alice@example.com",
        "_id": ObjectId("60e9fb4e7c1e7c2e6a16e6f9")
    }
})

orderedfalse 时,所有文档都会尝试插入,即使某些文档插入失败:

// 使用 insertMany 插入多个员工文档,ordered 为 false
db.employees.insertMany(employees, { ordered: false })

在这种情况下,第一个文档会成功插入,第二个文档由于重复键错误插入失败,但操作不会停止,返回结果会显示部分插入成功:

{
    "acknowledged": true,
    "insertedIds": [
        ObjectId("60e9fb7a7c1e7c2e6a16e6fa")
    ],
    "writeErrors": [
        {
            "index": 1,
            "code": 11000,
            "errmsg": "E11000 duplicate key error collection: test.employees index: email_1 dup key: { : \"alice@example.com\" }",
            "op": {
                "name": "Bob",
                "email": "alice@example.com",
                "_id": ObjectId("60e9fb7a7c1e7c2e6a16e6fb")
            }
        }
    ]
}

四、在不同编程语言中插入文档

除了在 MongoDB shell 中进行插入操作,我们还可以在各种编程语言中使用 MongoDB 驱动程序来插入文档。以下以 Node.js、Python 和 Java 为例进行介绍。

4.1 Node.js 中插入文档

首先,确保你已经安装了 mongodb 包:

npm install mongodb

以下是在 Node.js 中使用 mongodb 驱动插入单个文档的示例:

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

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

async function insertDocument() {
    try {
        await client.connect();
        const database = client.db('test');
        const collection = database.collection('documents');

        const document = { "message": "Hello, MongoDB from Node.js" };
        const result = await collection.insertOne(document);
        console.log('Inserted document with _id:', result.insertedId);
    } catch (e) {
        console.error(e);
    } finally {
        await client.close();
    }
}

insertDocument();

插入多个文档的示例:

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

const uri = "mongodb://localhost:27017";
const client = new MongoClient(uri);

async function insertManyDocuments() {
    try {
        await client.connect();
        const database = client.db('test');
        const collection = database.collection('documents');

        const documents = [
            { "message": "Document 1 from Node.js" },
            { "message": "Document 2 from Node.js" }
        ];
        const result = await collection.insertMany(documents);
        console.log('Inserted documents with _ids:', result.insertedIds);
    } catch (e) {
        console.error(e);
    } finally {
        await client.close();
    }
}

insertManyDocuments();

4.2 Python 中插入文档

安装 pymongo 库:

pip install pymongo

在 Python 中插入单个文档的示例:

from pymongo import MongoClient

# 连接字符串
uri = "mongodb://localhost:27017"
client = MongoClient(uri)

db = client.test
collection = db.documents

document = {"message": "Hello, MongoDB from Python"}
result = collection.insert_one(document)
print('Inserted document with _id:', result.inserted_id)

插入多个文档的示例:

from pymongo import MongoClient

uri = "mongodb://localhost:27017"
client = MongoClient(uri)

db = client.test
collection = db.documents

documents = [
    {"message": "Document 1 from Python"},
    {"message": "Document 2 from Python"}
]
result = collection.insert_many(documents)
print('Inserted documents with _ids:', result.inserted_ids)

4.3 Java 中插入文档

添加 mongodb - driver - sync 依赖到你的项目中,例如在 Maven 项目的 pom.xml 中添加:

<dependency>
    <groupId>org.mongodb</groupId>
    <artifactId>mongodb - driver - sync</artifactId>
    <version>4.4.0</version>
</dependency>

在 Java 中插入单个文档的示例:

import com.mongodb.client.MongoClients;
import com.mongodb.client.MongoClient;
import com.mongodb.client.MongoCollection;
import com.mongodb.client.MongoDatabase;
import org.bson.Document;

public class InsertDocumentExample {
    public static void main(String[] args) {
        try (MongoClient mongoClient = MongoClients.create("mongodb://localhost:27017")) {
            MongoDatabase database = mongoClient.getDatabase("test");
            MongoCollection<Document> collection = database.getCollection("documents");

            Document document = new Document("message", "Hello, MongoDB from Java");
            collection.insertOne(document);
            System.out.println("Inserted document with _id: " + document.get("_id"));
        }
    }
}

插入多个文档的示例:

import com.mongodb.client.MongoClients;
import com.mongodb.client.MongoClient;
import com.mongodb.client.MongoCollection;
import com.mongodb.client.MongoDatabase;
import org.bson.Document;

import java.util.ArrayList;
import java.util.List;

public class InsertManyDocumentsExample {
    public static void main(String[] args) {
        try (MongoClient mongoClient = MongoClients.create("mongodb://localhost:27017")) {
            MongoDatabase database = mongoClient.getDatabase("test");
            MongoCollection<Document> collection = database.getCollection("documents");

            List<Document> documents = new ArrayList<>();
            documents.add(new Document("message", "Document 1 from Java"));
            documents.add(new Document("message", "Document 2 from Java"));

            collection.insertMany(documents);
            System.out.println("Inserted documents with _ids: ");
            for (Document doc : documents) {
                System.out.println(doc.get("_id"));
            }
        }
    }
}

五、插入文档时的写入关注点

写入关注点(write concern)决定了 MongoDB 在确认写入操作完成之前需要等待的条件。通过调整写入关注点,可以在数据一致性和写入性能之间进行权衡。

5.1 写入关注点选项

  • w: 0:不等待服务器确认,写入操作立即返回。这种模式写入速度最快,但不保证数据是否真正写入到服务器,可能会丢失数据,适用于对数据一致性要求不高的场景,如日志记录。
  • w: 1:等待主节点确认写入操作,这是默认的写入关注点。保证数据至少写入到主节点,但在主节点故障转移期间可能会出现短暂的数据不一致。
  • w: "majority":等待大多数节点(超过一半的副本集成员)确认写入操作。这种模式提供了较高的数据一致性,适用于对数据一致性要求较高的场景,如金融交易数据的写入。

5.2 在插入操作中使用写入关注点

在 MongoDB shell 中,可以在 insertOneinsertMany 方法中指定写入关注点。例如,使用 w: "majority" 写入关注点插入单个文档:

db.products.insertOne(
    {
        "name": "Smartphone",
        "description": "A latest - model smartphone",
        "price": 800,
        "category": "Electronics"
    },
    {
        writeConcern: { w: "majority", wtimeout: 5000 }
    }
)

wtimeout 是可选参数,指定等待写入操作确认的最长时间(以毫秒为单位)。如果在指定时间内未收到足够节点的确认,将抛出超时错误。

在 Node.js 中使用 mongodb 驱动指定写入关注点插入多个文档的示例:

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

const uri = "mongodb://localhost:27017";
const client = new MongoClient(uri);

async function insertManyWithWriteConcern() {
    try {
        await client.connect();
        const database = client.db('test');
        const collection = database.collection('documents');

        const documents = [
            { "message": "Document 1 with write concern" },
            { "message": "Document 2 with write concern" }
        ];
        const result = await collection.insertMany(documents, {
            writeConcern: { w: "majority", wtimeout: 3000 }
        });
        console.log('Inserted documents with _ids:', result.insertedIds);
    } catch (e) {
        console.error(e);
    } finally {
        await client.close();
    }
}

insertManyWithWriteConcern();

六、插入文档的性能优化

在处理大量数据插入时,性能优化至关重要。以下是一些优化插入文档性能的方法。

6.1 批量插入

如前文所述,使用 insertMany 方法一次性插入多个文档可以减少与数据库的交互次数,从而提高性能。相比多次调用 insertOneinsertMany 可以显著减少网络开销。例如,在插入 1000 个文档时,使用 insertMany 只需要一次网络请求,而使用 insertOne 则需要 1000 次网络请求。

6.2 合理设置写入关注点

根据应用场景合理选择写入关注点。如果应用对数据一致性要求不高,可以选择 w: 0w: 1 来提高写入性能。但如果数据一致性至关重要,如涉及金融交易等场景,应选择 w: "majority",不过这可能会稍微降低写入速度。

6.3 索引优化

在插入数据之前,应谨慎考虑集合的索引。过多的索引会增加插入操作的开销,因为每次插入都需要更新相关的索引。如果可能,尽量在插入数据之后再创建索引。另外,确保索引的设计与查询需求相匹配,避免创建不必要的索引。

6.4 合理分配文档大小

尽量避免插入过大的文档。大文档在网络传输和存储时会消耗更多的资源,从而影响插入性能。如果数据量较大,可以考虑将其拆分成多个较小的文档,或者使用 GridFS 来存储大文件。

6.5 连接池的使用

在使用编程语言的 MongoDB 驱动时,合理使用连接池可以提高性能。连接池可以复用数据库连接,减少连接创建和销毁的开销。例如,在 Node.js 中,mongodb 驱动默认使用连接池,确保应用程序在高并发情况下能够高效地与 MongoDB 交互。

七、插入文档时的常见问题及解决方法

在插入文档过程中,可能会遇到一些常见问题。以下是这些问题及相应的解决方法。

7.1 重复键错误

如前文提到,当插入具有相同 _id 或唯一索引字段值的文档时,会抛出重复键错误。解决方法是确保插入文档的 _id 或唯一索引字段值的唯一性。如果需要更新已存在的文档而不是插入新文档,可以使用 updateOneupdateMany 方法。

7.2 写入超时

当设置了写入关注点并指定了 wtimeout 时,可能会出现写入超时错误。这可能是由于网络问题、服务器负载过高或副本集同步延迟等原因导致。解决方法包括检查网络连接、优化服务器性能、适当增加 wtimeout 的值等。

7.3 数据类型不匹配

如果插入的文档中某个字段的数据类型与集合的预期数据类型不匹配,可能会导致插入失败。例如,在期望是数值类型的字段中插入字符串。解决方法是确保插入文档的数据类型与集合的需求一致,可以在插入之前进行数据类型验证。

7.4 集合不存在

在尝试插入文档时,如果集合不存在,可能会出现错误。在 MongoDB 中,集合会在插入第一个文档时自动创建,但如果在代码中显式检查集合是否存在,可以使用 db.collectionExists() 方法。例如,在 MongoDB shell 中:

if (!db.collectionExists('myCollection')) {
    db.createCollection('myCollection');
}
db.myCollection.insertOne({ "message": "Inserting into new collection" });

通过深入了解 MongoDB 插入文档的各种方法、注意事项、性能优化及常见问题解决方法,开发人员能够更加高效、可靠地将数据插入到 MongoDB 数据库中,为构建强大的应用程序奠定坚实的基础。无论是小型项目还是大规模数据处理场景,掌握这些知识都将有助于充分发挥 MongoDB 的优势。