Inserting Documents: A Detailed Explanation of `insertOne` and `insertMany`
Inserting documents is the first step in writing data to MongoDB—mastering insertOne and insertMany is the foundation of data manipulation.
This course provides an in-depth look at various methods for inserting documents, error handling, performance optimization, and the Write Concern mechanism.
1. What You'll Learn
- Core Usage of
insertOneandinsertMany - The "ordered" option and batch insertion behavior
- Write Concern (w, j, wtimeout)
- Strategies for Handling _id Conflicts
- Performance optimizations for bulk inserts
- Insert special types such as date and ObjectId
- Troubleshooting Common Insertion Errors
2. A Data Engineer’s True Story
(1) Pain Point: Batch imports of 1 million records often fail
Alice is a data engineer at an e-commerce company who needs to migrate 1 million product records from MySQL to MongoDB:
"I used
insertManyto import 1 million product records, but the entire import failed at the 500,000th record due to a duplicate _id, wasting four hours. It’s either all or nothing—this is a disaster for incremental migration."
The problems she faces:
| Issue | Impact |
|---|---|
| ordered: true (default) | If one item in the batch fails, the entire batch fails |
| Lack of Write Concern | Data loss due to server failure |
| _id conflict | Failure due to duplicate auto-increment IDs in MySQL |
| Bulk Insertion | 1 million individual insertions took 1 hour |
(2) A Solution Using MongoDB and bulkWrite
// === Usage bulkWrite + ordered: false Resolving Partial Failure Issues ===
const products = [...]; // 100,000 product records
const BATCH_SIZE = 1000;
for (let i = 0; i < products.length; i += BATCH_SIZE) {
const batch = products.slice(i, i + BATCH_SIZE);
try {
await Product.bulkWrite(
batch.map(doc => ({
insertOne: { document: doc }
})),
{ ordered: false } // Allow for Partial Failure,Continue execution
);
} catch (err) {
console.error(`Batch ${i / BATCH_SIZE} Failure:${err.writeErrors?.length} items`);
}
}
// === Usage Write Concern Ensure Data Persistence ===
await Product.bulkWrite(
batch.map(doc => ({ insertOne: { document: doc } })),
{
ordered: false,
writeConcern: { w: 'majority', j: true, wtimeout: 5000 }
}
);
(3) Revenue
| Dimension | Single-row insertion | Bulk insertion + ordered: false |
|---|---|---|
| Performance | 1 million records ~60 minutes | 1 million records ~3 minutes |
| Fault Tolerance | Single failure results in loss | Partial failure allows continuation |
| Data Security | Prone to loss | Write Concern Persistence |
| Code Complexity | Simple | Moderate |
3. insertOne: Insert a Single Document
Concept Explanation: insertOne is the most basic write method in MongoDB, used to insert a document into a collection. Each document in MongoDB is stored in BSON format and is automatically assigned a unique _id primary key. Unlike INSERT INTO in relational databases, insertOne does not require a predefined table structure, and documents can contain any combination of fields.
How It Works: When a client initiates a insertOne request, the MongoDB server performs the following steps: validates the BSON format → checks the uniqueness of _id → writes to the WiredTiger storage engine → applies the Write Concern → returns insertedId. The entire write process is atomic for a single document.
sequenceDiagram
participant App as Applications
participant Mongod as MongoDB Server-side
participant WT as WiredTiger Engine
App->>Mongod: insertOne({ doc })
Mongod->>Mongod: Verification BSON Format
Mongod->>Mongod: Inspection _id Unique Index
alt _id Conflict
Mongod-->>App: E11000 duplicate key error
else _id The Only One
Mongod->>WT: Write to the document + Update Index
WT-->>Mongod: Confirm Write
Mongod-->>App: { acknowledged: true, insertedId }
end
| Parameter | Type | Description |
|---|---|---|
document |
Document | Document to insert (required) |
writeConcern |
Document | Write Confirmation Level (Optional) |
| Applicable Scenarios | Non-Applicable Scenarios |
|---|---|
| Creating a Single Record (User Registration) | Bulk Data Import |
Need to retrieve insertedId |
Bulk write of more than 1,000 records |
| Documents with Complex Nesting | Import with Duplicate Data Removal |
(1) Basic Syntax
// === insertOne Basic Usage ===
db.products.insertOne({
sku: "PHONE-001",
title: "Smartphone X",
price: NumberDecimal("599.99"),
category: "Electronics",
stock: 50,
createdAt: new Date()
});
// Return Results:
// {
// acknowledged: true,
// insertedId: ObjectId('507f1f77bcf86cd799439011')
// }
Key Points Analysis:
acknowledged: trueindicates that the write operation has been acknowledged by the MongoDB server (subject to Write Concern)- If
writeConcern: { w: 0 }, thenacknowledgedisfalse, andinsertedIdis not returned. - The value of
insertedIddepends on whether_idis specified manually—if not specified, an ObjectId is automatically generated.
(2) Analysis of Return Values
Concept Explanation: The return value of insertOne contains two key fields—acknowledged and insertedId. If acknowledged is true, it indicates that the write operation has been acknowledged by the MongoDB server (subject to Write Concern; if w: 0, then it is false). .insertedId is the _id value of the inserted document; it is returned regardless of whether _id is automatically generated or manually specified.
| Return Field | Type | Description | Notes |
|---|---|---|---|
acknowledged |
Boolean | Whether the write has been acknowledged | false when w: 0 |
insertedId |
ObjectId/Any | The _id of the inserted document | Returns the specified value when specified manually |
const result = db.products.insertOne({
sku: "TEST-001",
title: "Test Product"
});
print(result.acknowledged); // true(Write confirmed)
print(result.insertedId); // ObjectId('507f1f77bcf86cd799439012')
| Field | Type | Description |
|---|---|---|
acknowledged |
boolean | true indicates that the write has been confirmed |
insertedId |
ObjectId | The _id of the inserted document |
(3) _id is automatically generated
Concept Explanation: _id is the primary key of a MongoDB document, with a default type of ObjectId (12-byte binary). If _id is not specified manually, the MongoDB driver automatically generates it on the client side, ensuring it is unique before being written to the server. This design differs from MySQL’s auto-increment IDs—ObjectId does not rely on a centralized counter and inherently supports distributed environments.
How It Works: An ObjectId consists of a 4-byte timestamp + a 5-byte random value (machine + process) + a 3-byte incrementing counter. The timestamp portion ensures that ObjectIds are naturally sorted by insertion time; the random value guarantees uniqueness across processes; and the counter ensures uniqueness within a single second.
graph LR
A[Client-Side Generation ObjectId] --> B[Timestamp 4B<br/>Insertion Time]
A --> C[Random value 5B<br/>Machine+Unique Process]
A --> D[Counter 3B<br/>Increment within the same second]
B --> E[Globally Unique<br/>Naturally Ordered<br/>Withdrawal Time]
| _id Strategy | Example | Applicable Scenarios | Ordering |
|---|---|---|---|
| Auto ObjectId | ObjectId("...") |
General Scenarios (Default) | ✅ Sort by Time |
| String Business Key | "ORDER-2026-001" |
Order Number, SKU | Depends on format |
| Auto-increment | NumberInt(1) |
Legacy System Migration | ✅ Sort by Number |
| Timestamp | NumberLong(1700000000) |
Time-series data | ✅ Sorted by time |
| UUID | UUID("...") |
Cross-system unique | ❌ Unordered |
// === Not specified _id(Automatically Generated ObjectId)===
db.users.insertOne({
name: "Alice",
email: "alice@example.com"
});
// Automatically Generated _id: ObjectId('507f1f77bcf86cd799439011')
// === Specify manually _id ===
db.users.insertOne({
_id: "user_001", // String ID
name: "Alice"
});
db.users.insertOne({
_id: ObjectId(), // Generated Manually ObjectId
name: "Bob"
});
db.users.insertOne({
_id: NumberLong(1700000000000), // Timestamps as ID
name: "Charlie"
});
(4) _id Uniqueness
Concept Explanation: MongoDB automatically creates a unique index on the _id field of each collection, which serves as the fundamental guarantee of data integrity. The unique feature of the _id unique index is that it cannot be deleted—even if dropIndexes() is executed, the _id index remains. When a document with a duplicate _id is inserted, MongoDB throws a E11000 duplicate key error error, and the entire insertion operation is rolled back.
Use Cases: In data migration and bulk import scenarios, _id conflicts are the most common source of errors. Understanding how to prevent and resolve these conflicts is key to ensuring stable operation in production environments. The recommended prevention strategy is to query the existing set of _id before importing, or to use the upsert schema as an alternative to insertOne.
| Scenario | Cause of Conflict | Recommended Strategy |
|---|---|---|
| MySQL → MongoDB Migration | Auto-increment IDs Conflicting with Existing Data | Remove the old _id and let MongoDB generate it |
| Merging Data from Multiple Sources | Different data sources have the same business key | Add prefix: sourceA_ORDER-001 |
| Incremental Sync | Source data already exists in the destination | updateOne + upsert |
| Bulk Import | CSV/JSON with duplicate rows | ordered: false Skip duplicates |
// === _id Handling Repeated Errors ===
try {
db.users.insertOne({
_id: "user_001", // Already exists
name: "Alice Duplicate"
});
} catch (err) {
// E11000 duplicate key error collection: shopdb.users index: _id_
print("❌ _id Already exists:" + err.message);
}
// === Usage upsert Handling Duplicates ===
db.users.updateOne(
{ _id: "user_001" },
{ $set: { name: "Alice Updated" } },
{ upsert: true } // If it doesn't exist, insert it,If it exists, update it
);
▶ Example 1: Complete usage of insertOne
// === Inserting Different Types of Fields ===
db.products.insertOne({
// String
sku: "PHONE-X-256-BLK",
title: "Smartphone X 256GB Black",
// Numeric Types
price: NumberDecimal("599.99"), // Decimal128(Accurate)
stock: NumberInt(50), // Int32
viewCount: NumberLong(1000000), // Long
// Boolean
isActive: true,
isFeatured: false,
// Date
createdAt: new Date(),
releaseDate: ISODate("2026-01-01"),
// Array
tags: ["5g", "amoled", "fast-charging"],
colors: ["Black", "White", "Blue"],
// Nested Documents
specs: {
screen: "6.5 inch OLED",
battery: "4500mAh",
camera: "108MP"
},
// Binary
thumbnail: BinData(0, "iVBORw0KGgoAAAANSUhEUgAA..."),
// Null
discount: null
});
▶ Example 2: insertOne with different _id strategies
// === Strategy 1:Automatic ObjectId(Default)===
const r1 = db.users.insertOne({ name: "Alice", email: "alice@example.com" });
print(`Auto ObjectId: ${r1.insertedId}`);
// === Strategy 2:String Business Key ===
const r2 = db.orders.insertOne({
_id: "ORD-20260701-0001",
total: NumberDecimal("599.99"),
status: "pending"
});
print(`Business key: ${r2.insertedId}`);
// === Strategy 3:Nested Documents + Array ===
const r3 = db.products.insertOne({
_id: ObjectId(),
sku: "PHONE-X-256-BLK",
specs: { screen: "6.5 inch OLED", battery: "4500mAh" },
tags: ["5g", "amoled"],
price: NumberDecimal("599.99")
});
print(`Nested doc: ${r3.insertedId}`);
Output: Auto ObjectId: ObjectId('...') | Business key: ORD-20260701-0001 | Nested doc: ObjectId('...')
4. insertMany: Batch Insertion
Concept Description: insertMany—inserting multiple documents in a single operation—is the core method for batch data writing. Compared to the insertOne method, which processes documents one by one, insertMany combines multiple documents into a single network request sent to the server, significantly reducing network round-trip overhead and improving performance by 10 to 100 times.
How It Works: insertMany receives an array of documents and determines the execution strategy based on the ordered option. ordered: true (default) inserts items sequentially one by one and stops immediately upon encountering an error; ordered: false allows parallel insertion and skips failed items to continue execution. These two strategies have a significant impact on performance and data integrity.
graph TB
A[insertMany<br/>1000 Documents] --> B{ordered option}
B -->|ordered: true| C[Sequential Insertion<br/>Item 1 -> Item 2 -> ...<br/>Stop on Error]
B -->|ordered: false| D[Parallel Insertion<br/>Writing Multiple Rows Simultaneously<br/>Skip failed items]
C --> C1[Performance:Intermediate<br/>Consistency:Strong]
D --> D1[Performance:Higher<br/>Consistency:Weak]
style D fill:#d4edda
| Parameter | Type | Description |
|---|---|---|
documents |
Array | Array of documents (required, at least 1 entry) |
ordered |
Boolean | true Sequential execution (default), false Parallel execution |
writeConcern |
Document | Write Confirmation Level |
| Applicable Scenarios | Non-Applicable Scenarios |
|---|---|
| Data Migration, Bulk Import | Inserting a Single Document |
| Test Data Generation | Writes Requiring Strict Transaction Ordering |
| Batch Log Writing | Strong Dependencies Between Documents |
(1) Basic Syntax
// === insertMany Basic Usage ===
db.products.insertMany([
{ sku: "PHONE-001", title: "Phone A", price: 599.99 },
{ sku: "PHONE-002", title: "Phone B", price: 699.99 },
{ sku: "PHONE-003", title: "Phone C", price: 799.99 }
]);
// Return Results:
// {
// acknowledged: true,
// insertedIds: {
// '0': ObjectId('507f1f77bcf86cd799439011'),
// '1': ObjectId('507f1f77bcf86cd799439012'),
// '2': ObjectId('507f1f77bcf86cd799439013')
// },
// insertedCount: 3
// }
(2) The "ordered" option (crucial!)
Concept Explanation: ordered is the most critical option for insertMany. It determines how MongoDB handles errors during batch writes—whether to abort immediately or to skip the error and continue. Understanding ordered is crucial for data import in production environments.
Use Cases: For data migration and incremental synchronization scenarios, ordered: false is recommended because the source data may contain duplicates _id; skipping duplicates and continuing the import is more reasonable than having the entire batch fail. For financial transaction scenarios, ordered: true is recommended to ensure strict operational sequencing.
// === ordered: true(Default)— If it fails in the middle, stop ===
db.products.insertMany([
{ _id: 1, sku: "A" },
{ _id: 2, sku: "B" },
{ _id: 1, sku: "C" }, // ❌ _id Conflict
{ _id: 4, sku: "D" } // ⚠️ Will not be inserted(Previous failure)
]);
// Error:E11000 duplicate key error
// Actual insertion:A, B(2 items),C and D Not inserted
// === ordered: false — Skip failure,Continue execution ===
db.products.insertMany([
{ _id: 1, sku: "A" },
{ _id: 2, sku: "B" },
{ _id: 1, sku: "C" }, // ❌ _id Conflict
{ _id: 4, sku: "D" } // ✅ Still inserted
], { ordered: false });
// The error message lists all documents that failed to be indexed:
// BulkWriteError: 1 document(s) failed
// writeErrors: [
// { index: 2, code: 11000, errmsg: 'duplicate key' }
// ]
// Actual insertion:A, B, D(3 items),C Not inserted
(3) Comparison of the "ordered" options
| Dimension | ordered: true | ordered: false |
|---|---|---|
| Failure Type | Entire batch failed | Skip the failure and continue |
| Performance | Moderate | Faster (parallel) |
| Use Cases | Strong consistency (e.g., fund transfers) | Incremental import, logs |
| Error Message | First item failed | Details of all failures |
▶ Example 3: A Complete Guide to Batch Insertion
// === Example of Batch Importing E-commerce Products ===
const products = [
{ sku: "LAPTOP-001", title: "Laptop Pro", price: NumberDecimal("1299.99"), category: "Electronics", stock: 20 },
{ sku: "LAPTOP-002", title: "Laptop Air", price: NumberDecimal("999.99"), category: "Electronics", stock: 30 },
{ sku: "PHONE-001", title: "Smartphone X", price: NumberDecimal("599.99"), category: "Electronics", stock: 50 },
{ sku: "BOOK-001", title: "JavaScript Guide", price: NumberDecimal("29.99"), category: "Books", stock: 200 },
{ sku: "BOOK-002", title: "MongoDB Tutorial", price: NumberDecimal("34.99"), category: "Books", stock: 150 }
];
// === Default Mode(Orderly)===
try {
const result = db.products.insertMany(products);
print(`✅ Insert ${result.insertedCount} Items`);
} catch (err) {
print(`❌ Batch Failure:${err.message}`);
}
// === Fault-Tolerant Mode(Disorder)===
try {
const result = db.products.insertMany(products, { ordered: false });
print(`✅ Insert ${result.insertedCount} Items`);
} catch (err) {
print(`⚠️ Partial failure:Success ${err.result.insertedCount} items,Failure ${err.writeErrors.length} items`);
err.writeErrors.forEach(e => print(` Index of Failures ${e.index}: ${e.errmsg}`));
}
5. Write Concern
Concept Explanation: Write Concern is a write safety mechanism in MongoDB that defines "when a write operation is considered successful." It controls the number of replica nodes that must acknowledge the write operation before a response is returned to the client. This represents a core trade-off between data durability and write performance—the higher the confirmation level, the greater the safety, but the greater the latency.
How It Works: In a replica set architecture, write operations first arrive at the Primary node and are then asynchronously replicated to the Secondary nodes. The w parameter of Write Concern determines how many node confirmations must be awaited. w: 1 waits only for the Primary node to confirm (fastest but carries a risk of data loss), w: "majority" waits for a majority of nodes to confirm (recommended for production environments), and j: true ensures that the data has been written to the disk journal.
sequenceDiagram
participant App as Client
participant P as Primary
participant S1 as Secondary 1
participant S2 as Secondary 2
App->>P: insertOne({ doc }, { w: "majority" })
P->>P: Write to memory + Journal
P->>S1: Copy oplog
P->>S2: Copy oplog
S1-->>P: Confirm Write
S2-->>P: Confirm Write
Note over P: majority Reached(2/3 Node)
P-->>App: { acknowledged: true }
| Dimension | w: 0 | w: 1 | w: majority | w: majority + j: true |
|---|---|---|---|---|
| Number of Confirmation Nodes | No Wait | Primary | Majority of Nodes | Majority of Nodes + Disk |
| Performance | Fastest | Fast | Average | Slow |
| Data Security | Possible loss | Data may be lost if the primary server goes down | Virtually no loss | Most secure |
| Recommended Scenarios | Logging | Development | Production | Finance |
(1) What is Write Concern?
Write Concern describes the level of confirmation required for a write operation to be considered successful and determines when data is considered "saved."
graph LR
A[Client] -->|insertOne| B[mongod Receive]
B --> C{Write Concern Layout}
C -->|w: 1| D[Primary Write and Return]
C -->|w: majority| E[After most nodes have confirmed, return]
C -->|j: true| F[Returns only after writing to disk]
style E fill:#d4edda
style F fill:#d4edda
(2) Write Concern Parameters
| Parameter | Value | Description |
|---|---|---|
w |
0 / 1 / "majority" / Number |
Number of nodes with write confirmation |
j |
true / false |
Write journal to disk |
wtimeout |
Number of milliseconds | Timeout (default: wait indefinitely) |
(3) Comparison of Write Concern Levels
// === w: 0 — Do not wait for confirmation(Fastest,May be missing)===
db.products.insertOne(
{ sku: "TEST-001", title: "Test" },
{ writeConcern: { w: 0 } }
);
// Return Now,Write success is not guaranteed
// === w: 1 — Primary Node Confirmation(Default)===
db.products.insertOne(
{ sku: "TEST-002", title: "Test" },
{ writeConcern: { w: 1 } }
);
// Primary Write and Return
// === w: "majority" — Confirmed by a majority of nodes(Safest)===
db.products.insertOne(
{ sku: "TEST-003", title: "Test" },
{ writeConcern: { w: "majority", j: true, wtimeout: 5000 } }
);
// The replica waits until most nodes have written to disk before returning(Recommended Production Environment)
(4) Comparison of Write Concern Configurations
| Level | Performance | Data Security | Use Cases |
|---|---|---|---|
w: 0 |
⚡⚡⚡ Extremely fast | ❌ Prone to loss | Logs, temporary data |
w: 1 |
⚡⚡ Fast | ⚠️ May be lost | Standalone development |
w: majority |
⚡ Medium | ✅ Virtually no loss | Recommended for production environments |
w: majority, j: true |
⚠️ Slower | ✅✅ Safest | Finance, critical data |
▶ Example 4: Production-Level Write Concern Configuration
// === Cluster-Level Settings(Recommendations)===
db.adminCommand({
setDefaultRWConcern: 1,
defaultWriteConcern: { w: "majority", j: true, wtimeout: 10000 },
defaultReadConcern: { level: "majority" }
});
// === Single-Write Specification ===
db.orders.insertOne(
{ userId: "user_001", total: 599.99, items: [...] },
{ writeConcern: { w: "majority", j: true, wtimeout: 5000 } }
);
// === mongoose Settings ===
const OrderSchema = new mongoose.Schema({
userId: String,
total: mongoose.Schema.Types.Decimal128,
items: Array
}, {
writeConcern: { w: 'majority', j: true, wtimeout: 5000 }
});
6. _id Conflict Resolution Strategy
Concept Explanation: _id is the unique identifier for a MongoDB document, and the _id field in each collection automatically creates a unique index. When the _id of an inserted document matches that of an existing document, MongoDB throws a E11000 duplicate key error error. In scenarios such as data migration, bulk imports, and merging from multiple sources, _id conflicts are one of the most common issues.
How It Works: Before writing a document, MongoDB first checks whether the _id field violates the unique index constraint. If a conflict occurs, the entire write operation is rolled back (single-document atomicity), and error code 11000 is returned. Understanding the differences between various conflict resolution strategies is critical for data integrity in production environments.
graph TB
A[_id Conflict E11000] --> B[Strategy Selection]
B --> C[Ignore duplicates<br/>ordered: false]
B --> D[Overwrite the old value<br/>replaceOne + upsert]
B --> E[Partial Update<br/>updateOne + upsert]
B --> F[Regenerate _id<br/>Remove _id Field]
B --> G[Retry Mechanism<br/>Application-Layer Retry]
style E fill:#d4edda
| Strategy | Syntax | Data Integrity | Use Cases |
|---|---|---|---|
| Skip duplicates | ordered: false |
Keep old data | Incremental import, log |
| Overwrite old values | replaceOne + upsert |
Replace with new data | Full sync |
| Partial Update | updateOne + upsert |
Merge Old and New Data | Incremental Update Fields |
| Ignore _id | Delete _id field |
Insert all (new _id) | Import without duplicates |
| Retry Mechanism | Application-Level Retry | Depends on the New _id | Temporary Conflict |
(1) Error Symptoms
// === _id Repeated Mistakes ===
db.users.insertOne({ _id: 1, name: "Alice" });
// { acknowledged: true, insertedId: 1 }
db.users.insertOne({ _id: 1, name: "Bob Duplicate" });
// E11000 duplicate key error collection: shopdb.users index: _id_ dup key: { _id: 1 }
(2) 5 Management Strategies
graph TB
A[_id Conflict] --> B[Strategy 1<br/>Skip duplicates]
A --> C[Strategy 2<br/>Overwrite the old value]
A --> D[Strategy 3<br/>upsert Automatic Selection]
A --> E[Strategy 4<br/>Ignore _id Field]
A --> F[Strategy 5<br/>Retry Mechanism]
style D fill:#d4edda
(3) Strategy Implementation
// === Strategy 1:Usage ordered: false Skip duplicates ===
try {
db.users.insertMany(
[{ _id: 1, name: "Alice" }, { _id: 2, name: "Bob" }, { _id: 1, name: "Dup" }],
{ ordered: false }
);
} catch (err) {
print(`Skip ${err.writeErrors.length} Duplicate entry`);
}
// === Strategy 2:Usage replaceOne Coverage ===
db.users.replaceOne(
{ _id: 1 },
{ _id: 1, name: "Alice Updated", updatedAt: new Date() },
{ upsert: true }
);
// === Strategy 3:Usage updateOne + upsert ===
db.users.updateOne(
{ _id: 1 },
{ $set: { name: "Alice", email: "alice@example.com" } },
{ upsert: true } // If it doesn't exist, insert it,If it exists, update it
);
// === Strategy 4:When inserting, make sure to MongoDB Automatically Generated _id ===
const docs = externalData.map(d => {
const { _id, ...rest } = d; // Deconstruct and remove _id
return rest; // let MongoDB automatically generate _id
});
db.users.insertMany(docs);
// === Strategy 5:Retry Mechanism(Application Layer)===
async function insertWithRetry(doc, maxRetries = 3) {
for (let i = 0; i < maxRetries; i++) {
try {
return await db.collection('users').insertOne(doc);
} catch (err) {
if (err.code === 11000 && i < maxRetries - 1) {
// Generate a new one _id Retry
doc._id = new ObjectId();
continue;
}
throw err;
}
}
}
▶ Example 5: Bulk Import + Duplicate Removal Strategy
// === Scene:Import CSV Data,Part _id Already exists ===
const csvData = [
{ _id: "USER-001", name: "Alice", email: "alice@example.com" },
{ _id: "USER-002", name: "Bob", email: "bob@example.com" },
{ _id: "USER-001", name: "Alice Duplicate", email: "alice2@example.com" },
{ _id: "USER-003", name: "Charlie", email: "charlie@example.com" }
];
// === Plan A:Ignore duplicates,Insert new data only ===
const insertedIds = [];
const duplicates = [];
csvData.forEach(doc => {
try {
const result = db.users.insertOne(doc);
insertedIds.push(result.insertedId);
} catch (err) {
if (err.code === 11000) {
duplicates.push(doc._id);
} else {
throw err;
}
}
});
print(`✅ Insert ${insertedIds.length} new data entries`);
print(`⚠️ Skip ${duplicates.length} Duplicate entry:${duplicates.join(', ')}`);
// === Plan B:Duplicate Coverage,Update existing data ===
db.users.bulkWrite(
csvData.map(doc => ({
replaceOne: {
filter: { _id: doc._id },
replacement: doc,
upsert: true
}
})),
{ ordered: false }
);
7. Performance Optimization for Bulk Inserts
Concept Explanation: Performance bottlenecks in bulk inserts primarily stem from three areas: network round-trip overhead, index update overhead, and disk I/O overhead. By understanding these three bottlenecks and optimizing them one by one, you can reduce the time required to import 1 million records from 60 minutes to less than 3 minutes.
How It Works: Processing documents one by one insertOne triggers one network request, one index update, and one disk write each time. insertMany combines multiple documents into a single network request, reducing overhead by two-thirds. bulkWrite Further supports mixed operation types (insert+update+delete), completing them in a single request. Temporarily removing unnecessary indexes before import can further improve performance by 5 to 10 times.
graph LR
A[100,000 data entries] --> B[item by item insertOne<br/>~60 minutes<br/>100,000 web requests]
A --> C[insertMany 1000/batch<br/>~5 minutes<br/>1000 network requests]
A --> D[bulkWrite + dropIndexes<br/>~3 minutes<br/>1000 requests + Updates Without Indexes]
style D fill:#d4edda
| Optimization Strategies | Performance Improvements | Risks | Recommended Scenarios |
|---|---|---|---|
insertMany Replaces insertOne |
10–100x | None | All batch writes |
ordered: false |
1.5–3x | May skip failed entries | Fault-tolerant import |
| Temporarily delete indexes | 5–10x | Must be rebuilt after import | Initial import |
bulkWrite Replaces insertMany |
1.2–1.5x | None | Mixed operation |
w: 0 (No wait for confirmation) |
2–5x | Data may be lost | Temporary data, logs |
(1) Performance Comparison
graph LR
A[Insert 100,000 data entries] --> B[Insert one by one<br/>~60 minutes]
A --> C[insertMany 1000/batch<br/>~5 minutes]
A --> D[bulkWrite 1000/batch<br/>~3 minutes]
style D fill:#d4edda
(2) Optimization Strategies
// === Optimization 1:Reasonable Batch Size ===
const BATCH_SIZE = 1000; // Recommendations 500-5000
for (let i = 0; i < data.length; i += BATCH_SIZE) {
const batch = data.slice(i, i + BATCH_SIZE);
db.collection.insertMany(batch, { ordered: false });
}
// === Optimization 2:Usage bulkWrite Replace insertMany ===
await Collection.bulkWrite(
data.map(doc => ({ insertOne: { document: doc } })),
{ ordered: false }
);
// === Optimization 3:Disable Index(During the import)===
// ⚠️ Use with caution:After the import is complete, remember to rebuild the indexes.
db.products.dropIndexes();
db.products.insertMany(data);
// Rebuild Index
db.products.createIndex({ sku: 1 }, { unique: true });
// === Optimization 4:Usage Write Concern 0(Extremely fast but unsafe)===
db.products.insertMany(data, { writeConcern: { w: 0 } });
// ⚠️ For temporary data only,Not recommended for production
// === Optimization 5:Usage mongoose bulkWrite ===
const result = await Product.bulkWrite(
data.map(doc => ({
insertOne: { document: doc }
})),
{ ordered: false }
);
(3) Batch Size Selection
| Data Size | Recommended Batch Size | Reason |
|---|---|---|
| < 100 KB | 1,000–5,000 | Low network overhead |
| 100 KB - 1 MB | 500–2000 | Balance throughput and latency |
| > 1 MB | 100–500 | Avoid excessively large single requests |
| Very large document (nearly 16 MB) | 1-10 | The document itself is large |
▶ Example 6: High-Performance Data Import Script
// === Import 100 Product Data for 10,000 Items(Optimized Version)===
const fs = require('fs');
const readline = require('readline');
const { MongoClient } = require('mongodb');
async function importLargeDataset() {
const client = new MongoClient('mongodb://localhost:27017');
await client.connect();
const collection = client.db('shopdb').collection('products');
// 1. Temporarily Delete an Index(Import Speed ↑5x)
await collection.dropIndexes().catch(() => {});
await collection.createIndex({ sku: 1 }, { unique: true }); // Preserve the unique index(Duplicate Prevention)
// 2. Streaming Read CSV
const fileStream = fs.createReadStream('products.csv');
const rl = readline.createInterface({ input: fileStream });
let buffer = [];
const BATCH_SIZE = 2000;
for await (const line of rl) {
const [sku, title, price, category] = line.split(',');
buffer.push({
sku,
title,
price: price ? NumberDecimal(price) : null,
category,
createdAt: new Date()
});
if (buffer.length >= BATCH_SIZE) {
try {
await collection.insertMany(buffer, { ordered: false });
} catch (err) {
if (err.writeErrors) {
console.warn(`⚠️ Skip ${err.writeErrors.length} Duplicate entry`);
}
}
buffer = [];
}
}
// 3. Insert the remaining data
if (buffer.length > 0) {
await collection.insertMany(buffer, { ordered: false });
}
// 4. Rebuild Index
await collection.createIndex({ category: 1, price: 1 });
await collection.createIndex({ title: 'text' });
console.log(`✅ Import Complete`);
await client.close();
}
importLargeDataset().catch(console.error);
8. Special Types of Insertions
Concept Explanation: MongoDB’s BSON format supports a much wider range of data types than JSON. When inserting documents, using these special types correctly is key to avoiding data precision loss and type errors. The three most common precision issues are: (1) JavaScript’s Number is a double-precision floating-point number, 0.1 + 0.2 ≠ 0.3; (2) JSON does not have a date type, so new Date() is converted to a string JSON.stringify(); (3) JSON does not support binary data, so images and files cannot be stored directly.
How It Works: Before sending an insert request, the MongoDB driver (including mongosh and the Node.js driver) first serializes the JavaScript object into BSON. During this process, the Date object is serialized as a BSON Date type (64-bit millisecond timestamp), NumberDecimal() is serialized as Decimal128 (128-bit high precision), and Buffer is serialized as BSON Binary. Understanding this serialization process is key to using these special types correctly.
graph TB
A[JavaScript Object] --> B[Driver Serialization]
B --> C{Field Type Determination}
C -->|Date Object| D[BSON Date<br/>64-bit Millisecond timestamp]
C -->|NumberDecimal| E[BSON Decimal128<br/>128-bit High precision]
C -->|Number Constants| F[BSON Double<br/>64-bit Floating-point]
C -->|Buffer / BinData| G[BSON Binary<br/>Subtype + Byte Stream]
C -->|ObjectId| H[BSON ObjectId<br/>12 Byte]
C -->|null| I[BSON Null]
style E fill:#d4edda
style D fill:#d4edda
| Type | Syntax | Precision/Range | Typical Scenarios |
|---|---|---|---|
Date |
new Date() / ISODate("...") |
Millisecond precision | Timestamp, validity period |
Decimal128 |
NumberDecimal("0.30") |
34-digit decimal | Amount, precise calculation |
Int32 |
NumberInt(123) |
-2^31 ~ 2^31-1 | Counting, Inventory |
Long |
NumberLong(1700000000) |
-2^63 ~ 2^63-1 | Timestamp ID, large integer |
BinData |
BinData(0, "base64...") |
Any binary file | Images, PDFs |
ObjectId |
ObjectId() / new ObjectId() |
12 bytes | Document reference, primary key |
(1) Insert Date
// === Current Time ===
db.logs.insertOne({ event: "login", timestamp: new Date() });
// === Specified time ===
db.logs.insertOne({
event: "signup",
timestamp: ISODate("2026-07-01T10:30:00Z")
});
// === Create from a string ===
db.logs.insertOne({
event: "purchase",
timestamp: new Date("2026-07-01")
});
(2) Insert ObjectId
// === Automatically Generated ===
db.users.insertOne({ name: "Alice" });
// === Create Manually ===
db.users.insertOne({
_id: new ObjectId(),
name: "Bob"
});
// === Create from 24-digit hex string ===
db.users.insertOne({
_id: ObjectId("507f1f77bcf86cd799439011"),
name: "Charlie"
});
// === Created from a timestamp(Used for range queries)===
const startOfDay = ObjectId.createFromTime(
Math.floor(new Date('2026-07-01').getTime() / 1000)
);
db.orders.insertOne({
_id: startOfDay,
total: 999.99
});
(3) Inserting Nested Documents
// === Nested Objects ===
db.products.insertOne({
sku: "PHONE-001",
specs: {
screen: { size: "6.5", type: "OLED" },
battery: { capacity: "4500mAh", type: "Li-Po" }
}
});
// === Array ===
db.products.insertOne({
sku: "SHIRT-001",
sizes: ["S", "M", "L", "XL"],
colors: [
{ name: "Red", hex: "#FF0000" },
{ name: "Blue", hex: "#0000FF" }
]
});
▶ Example 7: Inserting a Composite Type
// === Order Documentation(Includes all special types)===
db.orders.insertOne({
_id: ObjectId(),
orderNumber: "ORD-20260701-0001",
// String + Value
userId: "user_001",
total: NumberDecimal("1299.99"),
tax: NumberDecimal("130.00"),
// Array + Nested
items: [
{ sku: "LAPTOP-001", qty: 1, price: NumberDecimal("1299.99") },
{ sku: "MOUSE-001", qty: 2, price: NumberDecimal("29.99") }
],
// Status
status: "pending",
isPaid: false,
// Date
createdAt: new Date(),
expectedDelivery: new Date(Date.now() + 7 * 24 * 60 * 60 * 1000), // 7 days
// Binary (PDF receipt)
receiptPdf: BinData(0, "JVBERi0xLjQKJ..."),
// Quote
shippingAddressId: ObjectId("507f1f77bcf86cd799439011"),
// Metadata
metadata: {
userAgent: "Mozilla/5.0...",
ipAddress: "192.168.1.1"
}
});
9. Troubleshooting Common Insertion Errors
Concept Explanation: Insert operations may fail for a variety of reasons—_id conflict (E11000), document validation failure (121), BSON document too large (16755), or invalid field name (2). Understanding error codes and handling strategies is essential for ensuring stable operation in a production environment. The basic principle of troubleshooting is: first check the error code → identify the cause of the error → select a handling strategy.
How It Works: MongoDB performs multi-layer validation before writing a document: BSON format validation → field name validation (no names starting with $, no .) → _id unique index validation → schema validation → document size validation (16MB) → Nested depth validation (100 levels). Failure at any level will prevent the write and return the corresponding error code.
Debugging Tips:
- Enable detailed logging:
db.adminCommand({ setParameter: 1, logComponentVerbosity: { write: { verbosity: 2 } } }) - View the slow query log:
db.system.profile.find().sort({ ts: -1 }).limit(5) - Check document size:
BSON.calculateObjectSize(doc)Returns the number of bytes - Check Nesting Depth: Custom
getDepth()Function
Production Environment Monitoring:
- Write Latency:
db.serverStatus().opLatencies.writesMonitor write latency trends - Write Throughput:
db.serverStatus().opcounters.insertMonitors the number of inserts per second - Current operation:
db.currentOp({ "op": "insert" })View the insert operation currently in progress
graph TB
A[insertOne Request] --> B{BSON Format Validation}
B -->|Failure| B1[Error Code 2<br/>Invalid field name]
B -->|Through| C{_id Single-Check Verification}
C -->|Conflict| C1[Error Code 11000<br/>Duplicate Keys]
C -->|Through| D{Schema Verification}
D -->|Failure| D1[Error Code 121<br/>Verification Failed]
D -->|Through| E{Document Size Verification}
E -->|More than16MB| E1[Error Code 16755<br/>The document is too large]
E -->|Through| F[Write successful ✅]
style F fill:#d4edda
style C1 fill:#f8d7da
style D1 fill:#f8d7da
| Error Code | Meaning | Root Cause | Resolution Strategy |
|---|---|---|---|
| 11000 | _id is a duplicate | A document with the same _id already exists | Use upsert or ordered: false |
| 121 | Document validation failed | Field value does not comply with schema rules | Check schema validation rules |
| 2 | Field name error | Field name starts with $ or contains . |
Rename the field |
| 16755 | BSON document too large | Document exceeds the 16 MB limit | Split the document or use GridFS |
| 14 | Write Concern Timeout | Replica node response timeout | Increase wtimeout or simplify w |
| 50 | Exceeds maximum BSON depth | More than 100 levels of nesting | Reduce the number of nesting levels |
(1) Error Code Reference Table
| Error Code | Meaning | Solution |
|---|---|---|
| 11000 | _id duplicate | Use upsert or ordered: false |
| 121 | Document validation failed | Check the schema validation rules |
| 2 | Invalid field name (e.g., starting with $) |
Rename the field |
| 16755 | BSON document is too large (>16MB) | Split the document or use GridFS |
| 14 | Write Concern Timeout | Increase wtimeout or simplify w |
| 50 | Exceeds max BSON depth | Reduce nesting levels |
(2) Debugging Tips
// === Enable detailed logging ===
db.adminCommand({ setParameter: 1, logComponentVerbosity: { write: { verbosity: 2 } } });
// === View the slow query log ===
db.system.profile.find().sort({ ts: -1 }).limit(5);
// === Check the document size ===
const doc = { /* your document */ };
print(`Document Size:${BSON.calculateObjectSize(doc)} bytes`);
print(`Nesting Depth:${getDepth(doc)}`);
(3) Performance Monitoring
// === View Current Database Operations ===
db.currentOp({ "op": "insert" });
// === Monitoring Write Performance ===
db.serverStatus().opcounters;
// {
// insert: 12345,
// query: 67890,
// update: 2345,
// delete: 100,
// ...
// }
// === View Write Latency ===
db.serverStatus().opLatencies.writes;
// { latency: 12345, ops: 10000 }
❓ FAQ
insertOne and insertMany?insertMany is 10 to 100 times faster than multiple insertOne calls because: (1) it reduces network round trips; (2) MongoDB processes them in batches on the server side; (3) it reduces the number of index updates. We recommend a batch size of 500 to 5,000 records.ordered: false offer better performance?ordered: true, MongoDB inserts data sequentially and stops if an error is detected; when ordered: false, it inserts data in parallel and simply skips the failed insert if an error is detected, resulting in better performance. ordered: false is recommended for production environments.wtimeout (e.g., 5 seconds).const { _id, ...rest } = doc) and let MongoDB generate it automatically. Alternatively, clear the existing _id field before importing.📖 Summary
- insertOne: Inserts a single document and returns acknowledged and insertedId
- insertMany: Bulk insertion; it is recommended to set
ordered: falseto skip failed rows - Write Concern controls the write confirmation level: w: 0 / 1 / majority + j: true
- There are 5 strategies for handling _id conflicts: skip / overwrite / upsert / ignore _id / retry
- Recommended batch size: 500–5,000 records per batch; this can improve performance by 10–100 times
- Performance optimization: bulkWrite + dropIndexes (during import) + Write Concern selection
- Common error codes: 11000 (duplicate), 121 (validation failed), 16755 (too large)
📝 Exercises
-
Basic Exercise (⭐): Use
insertOneto insert three product documents of different types (includingDecimal128,Date,Array, andObject), and verify the returnedinsertedId. -
Basic Question (⭐): Use
insertManyto insert 10 user documents at once, intentionally creating duplicate_idvalues, and compare the differences in results betweenordered: trueandordered: false. -
Advanced Exercise (⭐⭐): Write a script to batch-insert 1,000 product documents (with randomly generated SKUs, titles, and prices), using
ordered: falseand theWrite Concern w: majority, and record the insertion time. -
Advanced Problem (⭐⭐): Use
bulkWriteto implement the logic "update if the_idalready exists, otherwise insert" (upsert mode), processing 100 mixed records. -
Advanced Exercise (⭐⭐): Write a performance test script to compare the time taken for single-row inserts (1,000
insertOnecalls) and batch inserts (10insertManycalls, 100 rows per batch), and analyze the reasons for the performance differences. -
Challenge (⭐⭐⭐): Write a complete data migration tool that reads 1 million order records (including Decimal and DateTime fields) from MySQL, converts them to MongoDB BSON format, and imports them in bulk. The tool must support: (a) incremental synchronization; (b) retries upon failure; (c) progress display; (d) performance monitoring.



