MongoDB: ドキュメントの挿入:MongoDBの作成操作をマスターする
最終更新:2026-08-26
ドキュメントの挿入はMongoDBの最も基本的なCRUD操作の一つです。単一挿入、一括挿入、一括書き込み操作をマスターすることが重要です。
このチュートリアルでは、MongoDBのドキュメント挿入方法、書き込み確認(writeConcern)、エラーハンドリング、挿入パフォーマンスの最適化を深く学びます。
1. 学習内容
insertOne()で単一ドキュメントを挿入insertMany()で複数ドキュメントを一括挿入bulkWrite()で一括書き込み操作を実行- 書き込み確認(writeConcern)とエラーハンドリング
- ObjectIdの自動生成とカスタム主キー
- 挿入パフォーマンスの最適化
- mongooseでのドキュメント作成
2. フルスタックエンジニアの実例
(1) 課題:10万件の商品データをインポートするとタイムアウトする
EveはECプロジェクトのバックエンドエンジニアです:
「サプライヤーから10万件の商品データCSVを受け取り、MongoDBにインポートする必要があります。しかし、
insertMany()で一度に全てをインポートするとタイムアウトし、1件ずつinsertOne()で挿入すると3時間かかります。」
3つのアプローチを試みましたが、すべて失敗しました:
| アプローチ | 問題 |
|---|---|
❌ 一度に10万件をinsertMany() |
タイムアウト(最大バッチサイズ48MBを超過) |
❌ 1件ずつinsertOne() |
3時間以上、書き込みスループットが低すぎる |
❌ 1000件ごとにinsertMany() |
より良いが、最適なバッチサイズが不明 |
(2) bulkWriteによる解決策
バッチインサートと一括書き込み(bulkWrite)を使用して、書き込みスループットを最適化します。
// === Node.js一括インポートスクリプト(最適化版)===
const mongoose = require('mongoose');
const Product = require('./models/Product');
async function importProducts(products) {
// バッチサイズ1000でバッチインサート
const batchSize = 1000;
const batches = [];
for (let i = 0; i < products.length; i += batchSize) {
batches.push(products.slice(i, i + batchSize));
}
console.log(`📦 Importing ${products.length} products in ${batches.length} batches...`);
// 一括書き込み(ordered: false で失敗を許容)
let inserted = 0;
for (const batch of batches) {
const operations = batch.map(p => ({
insertOne: { document: p }
}));
const result = await Product.bulkWrite(operations, {
ordered: false, // 一部が失敗しても続行
writeConcern: { w: 1 } // ローカル開発用、本番では majority を使用
});
inserted += result.insertedCount;
console.log(`✅ Inserted ${inserted}/${products.length}`);
}
console.log(`🎉 Import complete: ${inserted} products`);
}
// 使用例
const products = []; // 100,000件の商品データ
await importProducts(products);
(3) 成果
| アプローチ | 所要時間 | スループット | エラーハンドリング |
|---|---|---|---|
| 単一インサート(insertOne) | 3時間 | 55 ops/s | 容易 |
| 一括インサート(insertMany) | タイムアウト | — | — |
| 一括書き込み(bulkWrite) | 5分 | 20,000 ops/s | 柔軟 |
3. insertOne:単一ドキュメントの挿入
概念概要:insertOne()はMongoDBで単一ドキュメントを挿入する最も基本的な方法です。操作は原子性を持ち、挿入が成功するか失敗するかのどちらかです。insertOne()は挿入されたドキュメントの_idを含む結果オブジェクトを返します。_idフィールドが指定されていない場合、MongoDBは自動的にObjectIdを生成します。
仕組み:insertOne()操作はドキュメントをBSON形式でシリアライズし、WiredTigerストレージエンジンに送信します。WiredTigerはドキュメントをディスクに書き込み、デフォルトのwriteConcernに従ってジャーナル(WAL)に記録し、操作が完了したことをクライアントに確認します。ドキュメントが挿入されると、MongoDBは_idフィールドにインデックスを自動的に作成します(主キーインデックス)。
sequenceDiagram
participant App as クライアント
participant Mongo as MongoDB
App->>Mongo: insertOne({ sku: "PHONE-001" })
Mongo->>Mongo: ObjectIdを生成
Mongo->>Mongo: BSONにシリアライズ
Mongo->>Mongo: WiredTigerに書き込み
Mongo->>Mongo: ジャーナル記録
Mongo-->>App: { acknowledged: true, insertedId: "..." }
(1) insertOneの基本
// === 単一ドキュメントの挿入 ===
db.products.insertOne({
sku: "PHONE-001",
title: "Smartphone X",
price: 599.99,
stock: 50
})
出力:
TEXT 📖 参照専用{ acknowledged: true, insertedId: ObjectId('507f1f77bcf86cd799439011') }
(2) カスタム主キー
ポイント解説:
- MongoDBは
_idフィールドが存在しない場合、自動的にObjectIdを生成 - カスタム主キーを指定可能 —
_id: "ORDER-001"など - カスタム主キーは一意である必要がある — 重複すると
MongoError: E11000 duplicate key error - ObjectIdと比較すると、カスタム主キーは可読性が高いが、タイムスタンプ情報を持たない
// === カスタム主キーを指定 ===
db.products.insertOne({
_id: "PHONE-001", // カスタム主キー
title: "Smartphone X",
price: 599.99
})
// 検索時はカスタム主キーを使用
db.products.findOne({ _id: "PHONE-001" })
▶ サンプル 1:insertOneの各種使用方法
// === 例1:最小限のドキュメント(自動生成_id)===
db.users.insertOne({ name: "Alice" })
出力:
TEXT 📖 参照専用{ acknowledged: true, insertedId: ObjectId('...') }
// === 例2:ネストされたドキュメント ===
db.users.insertOne({
name: "Bob",
email: "bob@example.com",
address: {
street: "123 Main St",
city: "Tokyo",
country: "Japan"
},
hobbies: ["reading", "coding", "hiking"]
})
出力:
TEXT 📖 参照専用{ acknowledged: true, insertedId: ObjectId('...') }
// === 例3:異なるBSONデータ型 ===
db.accounts.insertOne({
accountId: "ACC-001",
balance: NumberDecimal("12345.67"), // Decimal128
createdAt: new Date(), // Date
isActive: true, // Boolean
tags: ["premium", "verified"] // Array
})
4. insertMany:一括ドキュメントの挿入
概念概要:insertMany()は複数のドキュメントを単一の操作で挿入します。ループ内で複数回insertOne()を呼び出すよりも効率的です。デフォルトでは、insertMany()は順序付き挿入(ordered)で、あるドキュメントの挿入が失敗すると、後続のドキュメントは挿入されません。ordered: falseを指定すると、失敗したドキュメントがあっても続行します。
仕組み:insertMany()は内部的に複数のドキュメントを単一のバッチリクエストにパッケージ化し、MongoDBサーバーに送信します。サーバーはバッチを処理し、各ドキュメントを順番に挿入します(ordered: trueの場合)。最大バッチサイズは48 MBで、1回のinsertMany()で挿入できるドキュメント数はこの制限によって決まります。サーバー側のタイムアウト制限(デフォルト30秒)を超えないように注意してください。
graph LR
A[insertMany<br/>3ドキュメント] --> B[バッチリクエスト]
B --> C[MongoDBサーバー]
C --> D[ドキュメント1<br/>挿入]
C --> E[ドキュメント2<br/>挿入]
C --> F[ドキュメント3<br/>挿入]
D --> G[結果<br/>3件成功]
E --> G
F --> G
style B fill:#d4edda
| パラメータ | 説明 | デフォルト |
|---|---|---|
documents |
挿入するドキュメントの配列 | 必須 |
ordered |
失敗時に続行するか | true |
writeConcern |
書き込み確認レベル | デフォルト |
bypassDocumentValidation |
検証をスキップするか | false |
(1) insertManyの基本
// === 複数ドキュメントの一括挿入 ===
db.products.insertMany([
{ sku: "PHONE-001", title: "Smartphone X", price: 599.99 },
{ sku: "LAPTOP-001", title: "Laptop Y", price: 1299.99 },
{ sku: "TABLET-001", title: "Tablet Z", price: 499.99 }
])
出力:
TEXT 📖 参照専用{ acknowledged: true, insertedCount: 3, insertedIds: { '0': ObjectId('...'), '1': ObjectId('...'), '2': ObjectId('...') } }
(2) ordered: falseで失敗時に続行
ポイント解説:
ordered: true(デフォルト)— あるドキュメントが失敗すると、後続のドキュメントは挿入されませんordered: false— 失敗したドキュメントがあっても続行し、成功したドキュメントを挿入- 一括インポートシナリオでは
ordered: falseを推奨 — 一部のドキュメントの失敗が全体をブロックしないため - 書き込みパフォーマンス —
ordered: falseはordered: trueよりも高速(並列処理可能性)
// === ordered: falseで失敗時に続行 ===
db.products.insertMany([
{ _id: "PHONE-001", title: "Smartphone X" },
{ _id: "PHONE-001", title: "Duplicate ID" }, // 重複エラー
{ _id: "LAPTOP-001", title: "Laptop Y" }
], { ordered: false })
出力:
TEXT 📖 参照専用MongoBulkWriteError: E11000 duplicate key error Bulk write result: { insertedCount: 2, // 2件成功 insertedIds: { '0': 'PHONE-001', '2': 'LAPTOP-001' // インデックス2が成功 } }
▶ サンプル 2:大量データの一括インポート
// === 1000件のドキュメントを一括インポート ===
const products = [];
for (let i = 1; i <= 1000; i++) {
products.push({
sku: `PROD-${i.toString().padStart(5, '0')}`,
title: `Product ${i}`,
price: Math.random() * 1000,
stock: Math.floor(Math.random() * 100)
});
}
// バッチインサート
const result = db.products.insertMany(products, { ordered: false });
print(`Inserted ${result.insertedCount} products`);
5. bulkWrite:高度な一括書き込み操作
概念概要:bulkWrite()は最も柔軟な一括書き込みメソッドで、単一の操作で複数の種類の書き込み操作(insertOne、updateOne、updateMany、deleteOne、deleteMany、replaceOne)を混在できます。複数の異なる操作を単一のネットワークリクエストで送信でき、往復通信コストを大幅に削減します。bulkWrite()は一括データ同期、マイグレーション、複雑なデータ操作シナリオに非常に適しています。
仕組み:bulkWrite()は操作の配列を受け取り、順番に実行します(ordered: trueの場合)。各操作は独立しており、異なる種類(insertOne、updateOne、deleteOneなど)を持てます。結果オブジェクトには各操作タイプのカウントが含まれます(insertedCount、updatedCount、deletedCountなど)。ordered: falseを指定すると、MongoDBは並列処理可能性を高め、操作を並列で実行できます。
| 操作タイプ | 説明 | 使用場面 |
|---|---|---|
insertOne |
単一ドキュメントの挿入 | 新規作成 |
updateOne |
単一ドキュメントの更新 | 条件に基づく更新 |
updateMany |
複数ドキュメントの更新 | 一括更新 |
deleteOne |
単一ドキュメントの削除 | 条件に基づく削除 |
deleteMany |
複数ドキュメントの削除 | 一括削除 |
replaceOne |
単一ドキュメントの置換 | 全体置換 |
(1) bulkWriteの基本
// === 種類の混在一括書き込み ===
db.products.bulkWrite([
{ insertOne: { document: { sku: "NEW-001", title: "New Product" } } },
{ updateOne: {
filter: { sku: "PHONE-001" },
update: { $set: { price: 549.99 } }
}},
{ deleteOne: { filter: { sku: "OLD-001" } } }
])
出力:
TEXT 📖 参照専用{ insertedCount: 1, updatedCount: 1, deletedCount: 1, matchedCount: 1, modifiedCount: 1 }
(2) 一括インポートシナリオ
ポイント解説:
bulkWrite()はinsertMany()よりも柔軟 — 異なる種類の操作を混在可能insertOne操作はinsertMany()と同じパフォーマンス特性を持つordered: falseはパフォーマンスを向上させるが、エラー処理がより複雑になる- 一括インポートでは、バッチサイズを1000〜5000に制限することを推奨
// === 一括インポート(insertOneの配列)===
const operations = [];
for (let i = 1; i <= 5000; i++) {
operations.push({
insertOne: {
document: {
sku: `PROD-${i}`,
title: `Product ${i}`,
price: Math.random() * 1000
}
}
});
}
const result = db.products.bulkWrite(operations, { ordered: false });
print(`Inserted ${result.insertedCount} products`);
▶ サンプル 3:複雑な一括書き込み
// === アップサートを含む一括書き込み ===
db.products.bulkWrite([
// 新規作成
{ insertOne: { document: { sku: "PHONE-002", title: "Phone 2", price: 699 } } },
// 既存商品の価格を更新
{ updateOne: {
filter: { sku: "PHONE-001" },
update: { $inc: { price: 50 } }
}},
// アップサート(存在しない場合は作成)
{ updateOne: {
filter: { sku: "PHONE-003" },
update: { $set: { title: "Phone 3", price: 799 } },
upsert: true
}},
// 廃止商品を削除
{ deleteMany: {
filter: { status: "discontinued" }
}}
], { ordered: false })
出力:
TEXT 📖 参照専用{ insertedCount: 1, updatedCount: 1, upsertedCount: 1, deletedCount: 0, matchedCount: 2 }
6. 書き込み確認(writeConcern)
概念概要:書き込み確認(writeConcern)はMongoDBの重要な概念で、書き込み操作がいつ「成功」と見なされるかを定義します。{ w: 1 }は単一ノード(Primary)への書き込みで成功と見なします。{ w: "majority" }は大多数のノードへの書き込みで成功と見なします。{ w: 0 }は確認を待たず、最も高速ですが、データ損失のリスクが最も高いです。
仕組み:書き込み確認はMongoDBのレプリカセットアーキテクチャと密接に関連しています。Primaryノードが書き込み要求を受け取ると、データをローカルに書き込み、wパラメータに従って確認を待ちます。w: "majority"の場合、Primaryは大多数のSecondaryノードがデータを複製するまで待機します。これによりデータの耐久性を保証しますが、レイテンシーが増加します。j: true(journal)はジャーナルへの書き込みを待機し、クラッシュ後の復旧を保証します。
| 書き込み確認レベル | 説明 | パフォーマンス | 用途 |
|---|---|---|---|
{ w: 0 } |
確認なし | 最速 | ログ、テレメトリ |
{ w: 1 } |
Primaryノードへの確認 | 高速 | 開発環境 |
{ w: "majority" } |
大多数のノードへの確認 | 中程度 | 本番環境(推奨) |
{ j: true } |
ジャーナルへの書き込み | 低い | 金融取引 |
(1) 書き込み確認レベル
(2) 書き込み確認の指定
ポイント解説:
{ w: "majority" }— 本番環境推奨、データ損失を防ぐ{ w: 1 }— 開発環境、高速{ j: true }— ジャーナルへの書き込みを待機、最高の耐久性{ wtimeout: 5000 }— タイムアウトを5秒に設定
// === 書き込み確認を指定 ===
db.products.insertOne(
{ sku: "PHONE-001", title: "Smartphone X" },
{ writeConcern: { w: "majority", j: true } }
)
出力:
TEXT 📖 参照専用{ acknowledged: true, insertedId: ObjectId('...') }
// === タイムアウト付き書き込み確認 ===
db.products.insertOne(
{ sku: "PHONE-002", title: "Smartphone Y" },
{ writeConcern: { w: "majority", wtimeout: 5000 } }
)
7. エラーハンドリング
(1) よくあるエラー
概念説明:MongoDBの書き込み操作は複数の種類のエラーを生成する可能性があります。最も一般的なのはE11000 duplicate key error(一意制約違反)、MongoWriteConcernError(書き込み確認タイムアウト)、MongoNetworkError(接続問題)、MongoError(一般的なエラー)。適切なエラーハンドリングは本番アプリケーションに不可欠です。
| エラーコード | 説明 | 対応策 |
|---|---|---|
E11000 |
一意制約違反(重複キー) | アップサートまたはスキップ |
E12000 |
ドキュメント検証失敗 | ドキュメント構造を修正 |
Network timeout |
ネットワークタイムアウト | 再試行ロジック |
WriteConcern timeout |
書き込み確認タイムアウト | レプリカセット状態を確認 |
(2) エラーハンドリングの例
// === エラーハンドリング(Node.js)===
const mongoose = require('mongoose');
async function insertProduct(productData) {
try {
const product = await Product.create(productData);
console.log('✅ Product inserted:', product._id);
return product;
} catch (err) {
if (err.code === 11000) {
console.error('❌ Duplicate key error:', err.message);
// アップサートまたはスキップ
return await Product.findOneAndUpdate(
{ sku: productData.sku },
productData,
{ upsert: true, new: true }
);
} else if (err.name === 'ValidationError') {
console.error('❌ Validation error:', err.message);
throw err; // クライアントに返す
} else {
console.error('❌ Database error:', err);
throw err;
}
}
}
▶ サンプル 4:完全なエラーハンドリングスクリプト
// === 一括インポート(エラーハンドリング付き)===
async function bulkImport(products) {
const operations = products.map(p => ({
insertOne: { document: p }
}));
try {
const result = await Product.bulkWrite(operations, {
ordered: false,
writeConcern: { w: 1 }
});
console.log(`✅ Inserted ${result.insertedCount} products`);
return result;
} catch (err) {
if (err.name === 'MongoBulkWriteError') {
console.log('⚠️ Partial success:');
console.log(` Inserted: ${err.insertedCount}`);
console.log(` Errors: ${err.writeErrors.length}`);
// 失敗したドキュメントをログ
err.writeErrors.forEach(e => {
console.log(` Failed index ${e.index}: ${e.errmsg}`);
});
return err;
}
throw err;
}
}
8. mongooseでのドキュメント作成
概念概要:mongooseはMongoDBネイティブドライバーの上に、スキーマ定義、検証、ミドルウェアなどの高度な機能を提供します。mongooseのcreate()メソッドはinsertOne()と同様に動作しますが、スキーマ検証を自動的に実行します。insertMany()は複数ドキュメントを一括作成します。mongooseは_idを自動的に生成し、タイムスタンプ(createdAt、updatedAt)を管理します。
(1) mongooseモデルの定義
// === mongooseスキーマの定義 ===
const mongoose = require('mongoose');
const productSchema = new mongoose.Schema({
sku: { type: String, required: true, unique: true },
title: { type: String, required: true },
price: { type: Number, required: true, min: 0 },
stock: { type: Number, default: 0 },
category: { type: String, enum: ['Electronics', 'Clothing', 'Books'] },
isActive: { type: Boolean, default: true }
}, { timestamps: true }); // createdAt, updatedAtを自動管理
const Product = mongoose.model('Product', productSchema);
(2) mongooseでドキュメントを作成
ポイント解説:
Model.create()— 単一または複数ドキュメントを作成、スキーマ検証を実行Model.insertMany()— 複数ドキュメントを一括作成、検証を実行new Model()+doc.save()— インスタンスを作成し保存、ミドルウェア(pre/postフック)が実行されるtimestamps: true— mongooseが自動的にcreatedAtとupdatedAtを管理
// === 単一ドキュメントの作成 ===
const product = await Product.create({
sku: "PHONE-001",
title: "Smartphone X",
price: 599.99,
stock: 50
});
console.log(product._id); // ObjectId
console.log(product.createdAt); // 自動生成
console.log(product.updatedAt); // 自動生成
// === 複数ドキュメントの一括作成 ===
const products = await Product.insertMany([
{ sku: "LAPTOP-001", title: "Laptop Y", price: 1299.99 },
{ sku: "TABLET-001", title: "Tablet Z", price: 499.99 }
], { ordered: false });
// === インスタンスを作成して保存(ミドルウェア実行)===
const newProduct = new Product({
sku: "WATCH-001",
title: "Smart Watch",
price: 299.99
});
await newProduct.save(); // pre/postフックが実行される
▶ サンプル 5:mongoose一括インポート
// === mongoose一括インポート(エラーハンドリング付き)===
async function importProducts(productsData) {
try {
// スキーマ検証をスキップ(高速化)
const result = await Product.insertMany(productsData, {
ordered: false,
rawResult: true // 詳細な結果を取得
});
console.log(`✅ Imported ${result.insertedCount} products`);
return result;
} catch (err) {
if (err.name === 'MongoBulkWriteError') {
console.log(`⚠️ Partial import: ${err.insertedCount} succeeded`);
console.log(` Failed: ${err.writeErrors.length}`);
return err;
}
throw err;
}
}
// 使用例
const products = [];
for (let i = 1; i <= 10000; i++) {
products.push({
sku: `PROD-${i}`,
title: `Product ${i}`,
price: Math.random() * 1000
});
}
await importProducts(products);
9. 挿入パフォーマンスの最適化
概念概要:MongoDBの挿入パフォーマンスは複数の要因に影響されます:バッチサイズ、書き込み確認レベル、インデックス数、ドキュメントサイズ、ネットワークレイテンシーです。最適なパフォーマンスを得るには、バッチ挿入(insertMany、bulkWrite)を使用し、インデックス数を減らし、書き込み確認を{ w: 1 }に設定し、ドキュメントサイズを小さく保ちます。
(1) パフォーマンス最適化のヒント
ポイント解説:
- バッチサイズ — 1000〜5000件が最適、48 MB制限を超えないように
- 書き込み確認 — 本番環境では
{ w: "majority" }、開発環境では{ w: 1 } - インデックス数 — 各インデックスは書き込みパフォーマンスを低下させる
ordered: false— 失敗を許容し、並列処理可能性を高める- ドキュメント検証 — 一括インポートではスキップ可能(
bypassDocumentValidation: true)
| 最適化手法 | 説明 | 効果 |
|---|---|---|
| バッチインサート | 複数ドキュメントを単一リクエストで | 高 |
ordered: false |
失敗を許容し続行 | 中 |
| 書き込み確認調整 | { w: 1 }または{ w: 0 } |
高 |
| インデックス削除 | 不要なインデックスを削除 | 中 |
| ドキュメント検証スキップ | bypassDocumentValidation: true |
低 |
(2) パフォーマンステスト
// === パフォーマンステスト(1万件のドキュメント)===
async function performanceTest() {
const count = 10000;
const products = [];
for (let i = 1; i <= count; i++) {
products.push({
sku: `PROD-${i}`,
title: `Product ${i}`,
price: Math.random() * 1000
});
}
// テスト1:単一インサート
console.time('insertOne');
for (const p of products) {
await Product.create(p);
}
console.timeEnd('insertOne'); // ~30秒
// テスト2:バッチインサート
console.time('insertMany');
await Product.insertMany(products, { ordered: false });
console.timeEnd('insertMany'); // ~2秒
}
performanceTest();
❓ よくある質問
📖 まとめ
insertOne()は単一ドキュメントを、insertMany()は複数ドキュメントを一括挿入_idフィールドが指定されていない場合、MongoDBは自動的にObjectIdを生成ordered: falseを指定すると、失敗したドキュメントがあっても続行bulkWrite()は異なる種類の書き込み操作を混在可能- 書き込み確認(writeConcern)は書き込みの耐久性レベルを定義
- 本番環境では
{ w: "majority" }を、開発環境では{ w: 1 }を推奨 - mongooseの
create()はスキーマ検証を実行し、タイムスタンプを自動管理 - バッチサイズ1000〜5000件が挿入パフォーマンスに最適
📝 練習問題
-
基礎問題(⭐):mongoshで
productsコレクションに単一ドキュメント(sku、title、priceフィールドを含む)を挿入し、返されたinsertedIdを確認してください。 -
基礎問題(⭐):mongoshで
insertMany()を使用して5つのドキュメントを一括挿入し、insertedCountとinsertedIdsを確認してください。 -
応用問題(⭐⭐):Node.jsスクリプトを書き、mongooseを使用して100件のドキュメントを一括インポートし、エラーハンドリングを追加してください。
-
応用問題(⭐⭐):
bulkWrite()を使用して、3つのinsertOne操作と2つのupdateOne操作を単一のバッチで実行してください。 -
チャレンジ(⭐⭐⭐):CSVファイルから10,000件の製品データをインポートする完全なスクリプトを書き、バッチ処理、エラーハンドリング、進捗表示を実装してください。