← createCollection 创建集合 MongoDB 过滤查询 →

Node.js MongoDB 插入文档:insertOne与insertMany方法详解与实践

著
原创 2026-10-14 Node.js 已有人查阅

插入操作在Node.js与MongoDB协作中的位置

使用Node.js访问MongoDB时,官方驱动mongodb包承担了连接管理、命令下发、结果解析的全部职责。插入文档是数据写入的起点,也是后续查询、更新、聚合的前提。驱动提供的插入能力主要落在两个方法上:insertOne()负责单文档写入,insertMany()负责批量写入。理解这两个方法的边界与行为,比记住语法更重要。

在真实项目里,插入操作往往不是孤立的一步。它可能出现在用户注册、订单创建、日志落库、设备上报等场景中。不同的场景对写入方式的选择、异常处理、性能预期都有不同要求。本节课程围绕这些实际差异展开,而不是停留在API说明层面。

insertOne方法:单文档写入的语义与返回值

insertOne()的调用形式非常直接:

collection.insertOne(document)

参数document是一个普通JavaScript对象,代表要写入的文档。方法返回一个Promise,解析结果中包含insertedId字段,这是MongoDB为文档自动生成的_id值。如果不存在,MongoDB会在写入时自动创建;如果数据库不存在,同样会自动创建。这一点在开发阶段比较方便,但在生产环境中需要留意权限与命名规范。

从驱动版本4.x开始,insertOne()的返回对象还包含acknowledged字段,表示写入是否被确认。在默认的写关注级别下,acknowledged为true意味着服务端已经接收并应用了这次写入。

一个容易被忽略的细节是:如果传入的文档已经包含_id字段,驱动不会覆盖它,而是直接使用该值。这在数据迁移或幂等写入场景中很有用,但也要求开发者自行保证_id的唯一性。

insertMany方法:批量写入的机制与顺序问题

insertMany()接收一个文档数组:

collection.insertMany(documents)

返回结果中包含insertedCount和insertedIds。insertedCount表示实际写入的文档数量,insertedIds是一个以数组索引为键的对象,记录了每个文档生成的_id。

批量写入默认采用有序模式。也就是说,如果数组中第3个文档因为唯一索引冲突而失败,第4个及之后的文档不会继续写入。如果业务上允许部分成功,可以在第二个参数中传入{ordered:false},此时驱动会跳过失败项,继续处理后续文档。这个选项在日志采集、批量导入等场景中值得考虑。

另一个需要关注的点是批量大小的控制。虽然insertMany()可以一次接收大量文档,但驱动和MongoDB对单次请求有大小限制(默认16MB)。当文档数量或体积较大时,应当分批调用,而不是把所有数据塞进一个数组。

项目实例:员工的自动创建与单条插入

先看一个最小可运行的例子,文件命名为code01_create.js:

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

const url = "mongodb://localhost:27017";

async function createDatabase() {
    const client = new MongoClient(url);

    try {
        await client.connect();

        const database = client.db("tpointtech");
        const collection = database.collection("employees");

        await collection.insertOne({
            Name: "John Doe",
            Age: 30,
            Address: "New York"
        });

        console.log("Database and collection created successfully.");
    } finally {
        await client.close();
    }
}

createDatabase();

执行nodecode01_create.js后,终端输出Databaseandcollectioncreatedsuccessfully.。此时tpointtech数据库和employees已经存在。这个例子的价值在于演示自动建库建的机制,而不是插入本身。

项目实例:单条记录插入与insertedId使用

文件命名为code02_insertOne.js:

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

const url = "mongodb://localhost:27017";

async function insertRecord() {
    const client = new MongoClient(url);

    try {
        await client.connect();

        const database = client.db("tpointtech");
        const collection = database.collection("employees");

        const result = await collection.insertOne({
            Name: "Ajeet Kumar",
            Age: 28,
            Address: "Delhi"
        });

        console.log("1 record inserted.");
        console.log("Inserted Id:", result.insertedId);
    } finally {
        await client.close();
    }
}

insertRecord();

运行nodecode02_insertOne.js,输出类似:

1 record inserted.
Inserted Id: ObjectId("...")

这里result.insertedId的实际用途比打印更广。在订单创建流程中,拿到insertedId后可以立即写入关联表,或者返回给前端作为业务标识。建议在插入后保留这个值,而不是重新查询一次。

项目实例:批量插入员工数据

文件命名为code03_insertMany.js:

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

const url = "mongodb://localhost:27017";

async function insertRecords() {
    const client = new MongoClient(url);

    try {
        await client.connect();

        const database = client.db("tpointtech");
        const collection = database.collection("employees");

        const result = await collection.insertMany([
            { Name: "Mahesh Sharma", Age: 25, Address: "Ghaziabad" },
            { Name: "Tom Moody", Age: 31, Address: "California" },
            { Name: "Rohit Singh", Age: 19, Address: "Mumbai" },
            { Name: "Jack Ross", Age: 45, Address: "London" }
        ]);

        console.log(result.insertedCount + " records inserted.");
    } finally {
        await client.close();
    }
}

insertRecords();

运行nodecode03_insertMany.js,输出4recordsinserted.。

验证写入结果:使用MongoDBShell查看文档

插入完成后,可以在MongoDBShell中执行:

use tpointtech
db.employees.find()

返回结果类似:

[
  {
    _id: ObjectId("..."),
    Name: "Ajeet Kumar",
    Age: 28,
    Address: "Delhi"
  },
  ...
]

find()不带条件时返回中的全部文档。在排查中,建议配合countDocuments()确认数量,再用find()查看具体内容。

个人经验:插入方法选择的几个判断点

第一,单条插入不一定比批量插入慢。当业务本身就是逐条产生数据时,强行攒批会增加代码复杂度和内存占用。insertOne()在这种场景下更直接。

第二,批量插入时,如果文档之间没有顺序依赖,优先考虑{ordered:false}。这样可以避免一条脏数据导致整批失败。但要注意,关闭有序后,insertedIds中不会包含失败项的占位,需要结合错误信息定位。

第三,不要忽略finally中的client.close()。在脚本类程序中,忘记关闭连接会导致进程无常退出。在Web服务中,连接应当复用,而不是每次请求都新建MongoClient。

第四,写入关注级别(writeConcern)会影响插入的可靠性与延迟。默认级别在多数场景下够用,但涉及资金、订单等数据时,应当根据业务容忍度调整。

错误处理与类型安全

官方驱动在插入失败时会抛出异常。常见的错误包括:唯一索引冲突(错误码11000)、文档大小超限、连接超时、权限不足。建议在try块中捕获并区分处理,而不是统一打印。

try {
    await collection.insertOne({ Name: "重复用户", Email: "alan@ebingou.cn" });
} catch (error) {
    if (error.code === 11000) {
        console.error("唯一索引冲突,请检查重复字段。");
    } else {
        console.error("插入失败:", error.message);
    }
}

在TypeScript项目中,可以为文档定义接口,利用驱动提供的泛型参数约束类型:

interface Employee {
    Name: string;
    Age: number;
    Address: string;
}

const collection = database.collection<Employee>("employees");

这样在调用insertOne()时,编译器会检查字段类型与必填项,减少运行时错误。

本节课程知识要点

  • insertOne()用于单文档写入,返回insertedId与acknowledged。

  • insertMany()用于批量写入,返回insertedCount与insertedIds。

  • 与数据库在首次写入时自动创建,生产环境需关注权限与命名。

  • 批量写入默认有序,可通过{ordered:false}改为无序。

  • 单次批量写入需注意16MB限制,数据量大时应分批。

  • 插入异常需区分唯一索引冲突、连接问题、权限问题等类型。

  • TypeScript项目中建议使用泛型约束文档类型。

练习与思考

  1. 在insertMany()中构造一条包含重复_id的文档,观察有序与无序两种模式下的返回结果差异。

  2. 将批量插入的文档数量扩大到5000条,尝试分批写入,并对比一次性写入的耗时与内存占用。

  3. 为employees添加唯一索引,然后测试插入重复数据时的错误码与错误信息。

  4. 思考在用户注册场景中,插入成功后如何利用insertedId完成后续关联操作。

现在框架中的惯用写法

在Express或Koa项目中,通常会把MongoClient的初始化放在应用启动阶段,通过中间件或依赖注入把实例传递给路由处理函数。插入操作本身仍然调用insertOne()或insertMany(),但连接管理、错误捕获、日志记录会由框架层统一处理。这种分层方式比在每个路由中重复创建连接更符合工程实践。

← createCollection 创建集合 MongoDB 过滤查询 →
分享笔记 (共有 篇笔记)
验证码:
微信公众号