【入門】MongoDBコマンドの基本を分かりやすく紹介
はじめに:NoSQLデータベース「MongoDB」の世界へようこそ
データベースと聞くと、多くの方がRDB(リレーショナルデータベース)を思い浮かべるかもしれません。例えば、MySQLやPostgreSQLのように、決まった「表(テーブル)」があり、その表の中で「行(レコード)」と「列(フィールド)」を使ってデータを管理する仕組みです。しかし、世の中にはRDBだけではなく、さまざまな種類のデータベースが存在します。その一つが、今回学ぶNoSQLデータベースに分類されるMongoDBです。
MongoDBは、特にWebサービスやモバイルアプリケーション、リアルタイム分析など、変化の速い現代のデータ要件に対応するために広く使われています。RDBとはデータの格納方法が根本的に異なり、柔軟性が高いことが大きな特徴です。
この記事では、MongoDBをこれから学び始める方に向けて、MongoDBがどのようなものかという基本から、実際にデータを操作するための基本的なコマンドまでを、分かりやすく、そして丁寧にご紹介します。特に、データの作成(Create)、読み込み(Read)、更新(Update)、削除(Delete)という、データベース操作の根幹となる「CRUD操作」を中心に、豊富な具体例を交えて解説していきます。
約5000語というボリュームで、各コマンドの使い方やオプション、そしてそれらを組み合わせることで何ができるのかを深く掘り下げていきますので、この記事を読み終える頃には、MongoDBの基本的な操作が自信を持って行えるようになっているはずです。さあ、MongoDBの魅力的な世界への第一歩を踏み出しましょう。
MongoDBの基本概念を理解する
実際にコマンドを学ぶ前に、MongoDBがデータをどのように扱っているのか、いくつかの基本的な概念を理解しておきましょう。これにより、コマンドの意味や構造がより深く理解できるようになります。
ドキュメント (Document)
MongoDBにおけるデータの基本単位は「ドキュメント」です。RDBの「行(レコード)」に相当するものですが、RDBのように厳格な列(フィールド)の定義は必要ありません。ドキュメントはBSONという形式で表現されます。
BSON (Binary JSON) は、JSON (JavaScript Object Notation) の拡張形式です。JSONは人間が読みやすいテキスト形式ですが、BSONはコンピュータが効率的に処理できるようにバイナリ形式になっています。データ型もJSONより豊富で、日付型やバイナリデータ型なども扱えます。
ドキュメントは、キーと値のペアの集まりで構成されます。例えば、以下は一人のユーザーを表すドキュメントの例です。
json
{
"_id": ObjectId("60c72b2f9b1d8b3d4c8a0e1b"), // 自動生成される一意のID
"name": "山田 太郎",
"age": 30,
"email": "[email protected]",
"isActive": true,
"address": { // ネストされたドキュメント
"street": "青山1-1",
"city": "東京都"
},
"hobbies": ["読書", "旅行", "プログラミング"], // 配列
"createdAt": ISODate("2023-10-27T10:00:00.000Z") // 日付型
}
このように、ドキュメント内にはさまざまなデータ型(文字列、数値、真偽値、配列、ネストされたドキュメント、日付、ObjectIdなど)を含むことができます。また、異なるドキュメントであっても、含まれるフィールドの種類や構造が異なっていても構いません。これがMongoDBの「スキーマレス」と呼ばれる柔軟性の高さにつながっています。
ドキュメントには、デフォルトで_idという特別なフィールドが自動的に追加されます。この_idは、そのドキュメントを一意に識別するためのキーであり、すべてのドキュメントで必須かつユニーク(重複しない)である必要があります。通常はMongoDBが自動的にObjectIdという型で生成してくれますが、自分で値を指定することも可能です(ただしユニークである必要があります)。
コレクション (Collection)
複数のドキュメントをまとめたものが「コレクション」です。RDBの「表(テーブル)」に相当するものと考えてください。ただし、コレクション内のドキュメントは、RDBのテーブルの行のようにすべてが同じ構造である必要はありません。同じコレクション内に、異なるフィールドを持つドキュメントが混在していても構いません。
例えば、「ユーザー」に関するドキュメントを集めたusersコレクションや、「商品」に関するドキュメントを集めたproductsコレクションなどを作成してデータを整理します。
データベース (Database)
複数のコレクションをまとめたものが「データベース」です。これはRDBのデータベースと同じ概念です。一つのMongoDBサーバー内で、複数のデータベースを作成し、それぞれ独立したデータセットを管理することができます。例えば、Webアプリケーション用のデータベース、分析用のデータベースなどを分けることができます。
リレーショナルデータベースとの比較
ここで簡単にRDBと比較してみましょう。
| 特徴 | RDB (例: MySQL) | NoSQL (例: MongoDB) |
|---|---|---|
| データ構造 | 行と列を持つ固定スキーマのテーブル | キーと値を持つドキュメント(柔軟なスキーマ) |
| 正規化 | 通常、正規化してテーブルを分割する | 非正規化(エンベデッド)やリファレンスを組み合わせる |
| 関係性 | JOINでテーブル間の関係性を表現する | ドキュメント内への埋め込み(エンベデッド)またはリファレンスで表現する |
| スケーラビリティ | スケールアップが得意(性能向上) | スケールアウトが得意(サーバー追加) |
| クエリ言語 | SQL | MongoDB Query Language (BSONベース) |
MongoDBは、特にデータの構造が頻繁に変わる可能性のあるアプリケーションや、大量のデータを分散して処理したい場合に強みを発揮します。
これらの基本概念を頭に入れた上で、次に実際にMongoDBを操作するための環境準備と、コマンドラインツールの使い方を見ていきましょう。
MongoDBのインストールとシェルへの接続
MongoDBのコマンドを実行するには、まずMongoDBサーバーが動作している必要があります。そして、そのサーバーと通信するためのクライアントツールが必要です。一般的に、MongoDBサーバー (mongod) と、コマンドを実行するためのシェル (mongo または mongosh) を使います。
MongoDBのインストール
MongoDBのインストール方法はOSによって異なります。公式サイト(https://docs.mongodb.com/manual/installation/)を参照するのが最も確実ですが、主要なOSでの一般的なインストール方法を簡単に紹介します。
- macOS (Homebrew):
brew tap mongodb/brew->brew install mongodb-community - Windows: 公式サイトからインストーラーをダウンロードして実行
- Linux (apt/yumなど): 各ディストリビューションのリポジトリ設定を行い、パッケージマネージャーでインストール
インストールが完了したら、MongoDBサーバー (mongod) を起動します。多くの場合、デフォルト設定であれば以下のコマンドで起動できます(環境によっては設定ファイルやデータディレクトリの指定が必要です)。
bash
mongod --dbpath /path/to/data/directory
--dbpathには、MongoDBがデータを格納するディレクトリを指定します。このディレクトリは事前に作成しておく必要があります。
mongoシェル (mongosh) への接続
サーバーが起動したら、次にコマンドを実行するためのシェルを起動します。古いバージョンではmongoコマンド、新しいバージョン(MongoDB 5.0以降推奨)ではmongoshコマンドを使います。この記事では主にmongoshを想定して説明しますが、基本的なコマンドはmongoでも同様に動作します。
新しいターミナルを開き、以下のコマンドを実行します。
bash
mongosh
特にオプションを指定しない場合、デフォルトでlocalhostの27017番ポートで動作しているMongoDBサーバーに接続を試みます。接続に成功すると、以下のようなプロンプトが表示されます。
test>
このtest>は、現在選択されているデータベース名(デフォルトではtestデータベース)と、コマンド入力待ちであることを示しています。これでMongoDBサーバーに接続し、コマンドを実行する準備ができました。
シェルの終了: シェルを終了するには、exitと入力してEnterキーを押します。
javascript
exit
これでMongoDBの基本環境が整いました。いよいよ、実際にデータを操作するコマンドに入っていきましょう。
mongoシェル (mongosh) の基本操作コマンド
シェルに接続した状態で、まずはデータベースやコレクションを操作するための基本的なコマンドをいくつか見ていきましょう。
データベースの選択 (use)
MongoDBに接続すると、デフォルトでtestデータベースが選択されています。作業を行うデータベースを変更するには、useコマンドを使います。
例えば、mydatabaseという名前のデータベースを使いたい場合:
javascript
use mydatabase
もしmydatabaseという名前のデータベースが既に存在すればそれが選択され、存在しなければ新しく作成されます。ただし、新しいデータベースは、最初のドキュメントを挿入するまではファイルシステム上には作成されません。
useコマンドを実行すると、現在選択されているデータベース名がプロンプトに表示されます。
switched to db mydatabase
mydatabase>
現在のデータベースの確認
現在どのデータベースを選択しているか確認したい場合は、dbコマンドを使います。
javascript
db
これは現在選択されているデータベースオブジェクトを返します。例えばmydatabaseを選択していれば、mydatabaseと表示されます。
データベースの一覧表示 (show dbs)
MongoDBサーバー上に存在するデータベースの一覧を表示するには、show dbsコマンドを使います。
javascript
show dbs
実行すると、以下のようにデータベース名とサイズが表示されます。
admin 40.00 KiB
config 108.00 KiB
local 72.00 KiB
mydatabase 4.00 KiB // use mydatabase で作成した場合
admin, config, localはMongoDB内部で使用されるデータベースです。
コレクションの一覧表示 (show collections)
現在選択しているデータベースの中に存在するコレクションの一覧を表示するには、show collectionsコマンドを使います。
mydatabaseを選択している状態で実行してみましょう。まだ何もコレクションを作成していない場合は、何も表示されないか、システムが使用するコレクション(例えばsystem.indexesなど)が表示されることがあります。
javascript
mydatabase> show collections
新しいコレクションは、最初のドキュメントを挿入する際に自動的に作成されます。次に学ぶデータの作成コマンドで、実際にコレクションを作成してみましょう。
これらの基本的なコマンドで、データベースやコレクションの存在を確認したり、作業対象を切り替えたりすることができます。ここからが本番、データの操作です!
ドキュメントの操作 (CRUD操作)
データベース操作の核心は、データの作成(Create)、読み込み(Read)、更新(Update)、削除(Delete)です。これをまとめてCRUD操作と呼びます。MongoDBでは、これらの操作は主にコレクションオブジェクトに対して行います。
例えば、現在選択しているデータベースがmydatabaseで、その中のusersコレクションを操作したい場合、シェルではdb.usersというようにアクセスします。
db.コレクション名.操作()という形式でコマンドを実行するのが基本です。
1. ドキュメントの作成 (Create)
新しいドキュメントをコレクションに挿入します。単一のドキュメントを挿入するinsertOne()と、複数のドキュメントを一度に挿入するinsertMany()があります。
まず、mydatabaseを選択していることを確認してください(use mydatabase)。
insertOne(): 単一ドキュメントの挿入
javascript
db.users.insertOne({
name: "山田 太郎",
age: 30,
city: "東京都",
isActive: true
})
このコマンドを実行すると、usersコレクションに指定したドキュメントが1つ挿入されます。_idフィールドを指定しなかったため、MongoDBが自動的にObjectIdを生成して付与してくれます。
成功すると、以下のような結果が返されます。
json
{
acknowledged: true,
insertedId: ObjectId("...") // 生成された_id
}
acknowledged: trueは操作が成功したことを示します。insertedIdは挿入されたドキュメントの_idです。
自分で_idを指定して挿入することも可能です。ただし、その値はコレクション内でユニークである必要があります。
javascript
db.products.insertOne({
_id: "product_A101", // 自分で指定したID (文字列)
name: "Laptop",
price: 120000,
tags: ["electronics", "computer"]
})
insertMany(): 複数ドキュメントの挿入
複数のドキュメントを配列として渡し、一度に挿入します。
javascript
db.users.insertMany([
{
name: "鈴木 花子",
age: 25,
city: "大阪府",
isActive: false
},
{
name: "佐藤 健太",
age: 35,
city: "愛知県",
email: "[email protected]" // 別のフィールドが含まれても良い
}
])
このコマンドは、指定した2つのドキュメントをusersコレクションに挿入します。それぞれのドキュメントに自動的に_idが付与されます。
成功すると、以下のような結果が返されます。
json
{
acknowledged: true,
insertedIds: {
'0': ObjectId("..."), // 1つ目のドキュメントの_id
'1': ObjectId("...") // 2つ目のドキュメントの_id
}
}
様々なデータ型の挿入:
MongoDBの柔軟性を活かして、様々なデータ型を含むドキュメントを挿入してみましょう。
javascript
db.sampleData.insertOne({
stringField: "Hello, MongoDB!",
numberField: 123,
doubleField: 3.14,
booleanField: true,
arrayField: ["apple", "banana", "cherry"],
objectField: { // ネストされたドキュメント
key1: "value1",
key2: 99
},
dateField: new Date(), // 現在の日付と時刻
objectIdField: new ObjectId(), // 新しいObjectId
nullField: null,
undefinedField: undefined // MongoDBに保存されない
})
new Date()で現在時刻を、new ObjectId()で新しいObjectIdを生成して挿入できます。undefinedフィールドはMongoDBのドキュメントとしては保存されません。
挿入操作は基本的に成功しますが、ネットワークの問題や、ユニーク制約に違反する_idを指定した場合などにエラーが発生する可能性があります。
2. ドキュメントの読み込み (Read)
コレクションからドキュメントを検索して取得します。主にfind()メソッドとfindOne()メソッドを使います。find()は条件に一致するすべてのドキュメントを返し、findOne()は条件に一致する最初のドキュメントのみを返します。
find(): ドキュメントの検索
find()メソッドの基本的な構文は db.collection.find(query, projection) です。
query(必須): 検索条件を指定するドキュメントです。条件を指定しない場合({}または引数なし)は、コレクション内のすべてのドキュメントを取得します。projection(任意): 取得するフィールドを指定するドキュメントです。必要なフィールドだけを取得することで、ネットワーク負荷を軽減できます。
全てのドキュメントを取得:
usersコレクションの全てのドキュメントを取得します。
javascript
db.users.find()
結果が長く表示される場合は、pretty()メソッドを追加すると整形されて見やすくなります。
javascript
db.users.find().pretty()
クエリセレクタ: 検索条件の指定
find()の最初の引数であるqueryドキュメントで、検索条件を指定します。{ field: value } の形式で記述します。
特定のフィールドが特定の値に一致するドキュメント:
nameフィールドが “山田 太郎” のドキュメントを検索します。
javascript
db.users.find({ name: "山田 太郎" })
ageフィールドが 25 のドキュメントを検索します。
javascript
db.users.find({ age: 25 })
複数の条件をANDで指定:
複数のフィールドに対して条件を指定する場合、カンマ区切りで記述すると、それらの条件が全てANDで結合されます。
cityが “東京都” かつ isActiveが true のドキュメントを検索します。
javascript
db.users.find({ city: "東京都", isActive: true })
比較演算子
特定のフィールドの値が、指定した値と等しいか、より大きいか、範囲内にあるかなどを条件にするには、比較演算子を使います。演算子はフィールド名の値として、{ $operator: value } の形式で指定します。
$eq: 等しい (Equal) –{ field: value }と同じ意味です。$ne: 等しくない (Not Equal)$gt: より大きい (Greater Than)$lt: より小さい (Less Than)$gte: より大きいか等しい (Greater Than or Equal To)$lte: より小さいか等しい (Less Than or Equal To)$in: 指定された値のいずれかに含まれる (In)$nin: 指定された値のいずれにも含まれない (Not In)
例:
ageが 30 より大きいドキュメント:
javascript
db.users.find({ age: { $gt: 30 } })ageが 20 以上 30 以下のドキュメント:
javascript
db.users.find({ age: { $gte: 20, $lte: 30 } }) // $gte と $lte をANDで組み合わせるcityが “東京都” または “大阪府” のドキュメント:
javascript
db.users.find({ city: { $in: ["東京都", "大阪府"] } })cityが “東京都” でも “大阪府” でもないドキュメント:
javascript
db.users.find({ city: { $nin: ["東京都", "大阪府"] } })
論理演算子
複数の条件をORやNOTなどで結合するには、論理演算子を使います。演算子はフィールド名の値としてではなく、クエリドキュメントのトップレベルに{ $operator: [ { condition1 }, { condition2 }, ... ] } の形式で記述することが多いです。
$and: 全ての条件が真 (AND) – デフォルトのカンマ区切りと同じですが、より複雑なAND条件に使うことがあります。$or: いずれかの条件が真 (OR)$not: 条件が偽 (NOT)$nor: 全ての条件が偽 (NOR)
例:
ageが 30 より大きい またはcityが “大阪府” のドキュメント:
javascript
db.users.find({
$or: [
{ age: { $gt: 30 } },
{ city: "大阪府" }
]
})ageが 30 以上 かつcityが “東京都” またはisActiveがtrueのドキュメント (複雑な例):
javascript
db.users.find({
$and: [
{ age: { $gte: 30 } },
{
$or: [
{ city: "東京都" },
{ isActive: true }
]
}
]
})ageが 30 ではないドキュメント:
javascript
db.users.find({
age: { $not: { $eq: 30 } } // ageが30と等しいという条件の否定
})
// またはシンプルに $ne を使う:
// db.users.find({ age: { $ne: 30 } })ageが 30 より小さい かつisActiveがfalseの両方が偽であるドキュメント (つまり、age >= 30またはisActive == trueのドキュメント):
javascript
db.users.find({
$nor: [
{ age: { $lt: 30 } },
{ isActive: false }
]
})
要素演算子
フィールド自体の存在や型を条件にする演算子です。
$exists: 指定されたフィールドが存在するかどうか (trueまたはfalse)$type: 指定されたフィールドのBSON型
例:
emailフィールドが存在するドキュメント:
javascript
db.users.find({ email: { $exists: true } })emailフィールドが存在しないドキュメント:
javascript
db.users.find({ email: { $exists: false } })ageフィールドが数値型 (16はBSONのInt32型、1はDouble型) のドキュメント:
javascript
db.users.find({ age: { $type: 16 } })
// または型名の文字列で指定:
// db.users.find({ age: { $type: "int" } }) // "double", "string", "array", "object", "date" など
MongoDBのBSON型コードは公式ドキュメントを参照してください。よく使うのは文字列名での指定です。
配列演算子
ドキュメント内の配列フィールドに対する条件を指定する演算子です。
$all: 配列が指定された値の全てを含む$elemMatch: 配列内に、指定された複数の条件を同時に満たす要素が少なくとも1つ存在する$size: 配列の要素数が指定された数と等しい
例:
productsコレクションに以下のドキュメントがあるとする。
json
{ "_id": 1, "name": "Product A", "tags": ["electronics", "computer", "laptop"] }
{ "_id": 2, "name": "Product B", "tags": ["electronics", "mobile"] }
{ "_id": 3, "name": "Product C", "tags": ["computer", "accessories"] }
{ "_id": 4, "name": "Product D", "tags": ["electronics"] }
tags配列に “electronics” と “computer” の両方が含まれるドキュメント:
javascript
db.products.find({ tags: { $all: ["electronics", "computer"] } })
// -> Product A が一致tags配列に “electronics” または “computer” のいずれかが含まれるドキュメント:
javascript
db.products.find({ tags: { $in: ["electronics", "computer"] } })
// -> Product A, Product B, Product C, Product D が一致tags配列の要素数が 3 のドキュメント:
javascript
db.products.find({ tags: { $size: 3 } })
// -> Product A が一致
もう少し複雑な例として、studentsコレクションに以下のドキュメントがあるとする。
json
{ "_id": 1, "name": "Alice", "scores": [ { "type": "exam", "score": 90 }, { "type": "quiz", "score": 80 } ] }
{ "_id": 2, "name": "Bob", "scores": [ { "type": "exam", "score": 85 }, { "type": "quiz", "score": 95 } ] }
{ "_id": 3, "name": "Charlie", "scores": [ { "type": "exam", "score": 70 }, { "type": "quiz", "score": 75 } ] }
scores配列の中に、typeが “exam” かつscoreが 85 より大きい要素が少なくとも1つ存在するドキュメント:
javascript
db.students.find({
scores: {
$elemMatch: {
type: "exam",
score: { $gt: 85 }
}
}
})
// -> Alice が一致 (exam: 90)
$elemMatchは、配列内の特定の要素が複数の条件を同時に満たす必要がある場合に非常に役立ちます。単に{ "scores.type": "exam", "scores.score": { $gt: 85 } }としてしまうと、例えば Alice のドキュメントでexamのスコアが 90、quizのスコアが 80 の場合でも一致してしまいます。なぜなら、scores.typeには “exam” があり、scores.scoreには 90 (>85) があるためです。しかし、$elemMatchを使えば、typeが “exam” でかつscoreが 85 より大きいという1つの要素を探すことになります。
ネストされたドキュメントのクエリ
ドキュメント内に別のドキュメントが埋め込まれている場合、ドット表記 (.) を使ってネストされたフィールドにアクセスできます。
usersコレクションに以下のようなドキュメントがあるとする。
json
{
"_id": ...,
"name": "山田 太郎",
"address": {
"street": "青山1-1",
"city": "東京都",
"zip": "107-0061"
}
}
addressサブドキュメントのcityフィールドが “東京都” のドキュメント:
javascript
db.users.find({ "address.city": "東京都" })addressサブドキュメントのzipフィールドが存在するドキュメント:
javascript
db.users.find({ "address.zip": { $exists: true } })
正規表現による検索
特定のパターンに一致する文字列を検索するには、正規表現を使います。
nameフィールドが “山” で始まるドキュメント:
javascript
db.users.find({ name: /^山/ }) // 正規表現リテラル
// または $regex 演算子を使う:
// db.users.find({ name: { $regex: "^山" } })emailフィールドに “example.com” を含むドキュメント(大文字小文字を区別しない場合):
javascript
db.users.find({ email: /example\.com/i }) // i は大文字小文字を区別しないオプション
// または $regex 演算子と $options を使う:
// db.users.find({ email: { $regex: "example\\.com", $options: "i" } })
正規表現を使うと柔軟な検索ができますが、パフォーマンスに影響を与える可能性があるため、大量データに対して頻繁に使用する場合は注意が必要です。可能であれば、完全一致や範囲検索など、インデックスが効きやすい条件を使う方が効率的です。
findOne(): 単一ドキュメントの検索
findOne()は、find()と同様にクエリを指定できますが、条件に一致するドキュメントのうち最初の1件のみを返します。ドキュメントが存在しない場合はnullを返します。
_idを指定して、特定のドキュメントをピンポイントで取得する際によく使われます。
“`javascript
// _id が ObjectId(“…”) のドキュメントを取得
db.users.findOne({ _id: ObjectId(“60c72b2f9b1d8b3d4c8a0e1b”) })
// name が “山田 太郎” のドキュメントを1件だけ取得
db.users.findOne({ name: “山田 太郎” })
“`
取得結果に対する操作(カーソルメソッド)
find()メソッドは、一致するドキュメントのカーソルを返します。このカーソルオブジェクトに対して、さらに様々な操作を行うメソッドをチェインすることができます。
pretty(): 結果を整形して表示する(シェルでのみ有効)limit(n): 取得するドキュメントの数を最大n件に制限するskip(n): 先頭からn件のドキュメントをスキップする(ページネーションに利用)sort({ field: 1 or -1 }): 指定したフィールドで結果をソートする (1は昇順、-1は降順)projection({ field1: 1, field2: 0, ... }): 取得するフィールドを制限する
例:
ageが 25 以上のドキュメントを、年齢の昇順で並べて、先頭から5件だけ取得する:
javascript
db.users.find(
{ age: { $gte: 25 } } // クエリ条件
).sort(
{ age: 1 } // 年齢で昇順ソート
).limit(
5 // 5件に制限
).pretty() // 整形表示
これらのメソッドは任意の順序でチェインできますが、通常はfind()->sort()->skip()->limit()の順で使用します(パフォーマンス上、ソートやスキップはフィルタリング後に適用されるべきだからです)。
projection: 取得フィールドの指定
find()の2つ目の引数で、取得したいフィールドを指定できます。
{ field1: 1, field2: 1, ... } の形式で、取得したいフィールドに1を指定します。指定しなかったフィールドは取得されません。
{ field1: 0, field2: 0, ... } の形式で、取得したくないフィールドに0を指定します。指定しなかったフィールドは取得されます。
ただし、_idフィールドはデフォルトで取得されます。_idを取得したくない場合は、明示的に { _id: 0 } を指定する必要があります。
例:
usersコレクションの全てのドキュメントから、nameとcityフィールドのみを取得する:
javascript
db.users.find({}, { name: 1, city: 1 })
// _id も取得されるので、実際は _id, name, city が取得されるusersコレクションの全てのドキュメントから、_idフィールド以外を取得する:
javascript
db.users.find({}, { _id: 0, name: 1, age: 1, city: 1, isActive: 1, email: 1, ... /* 取得したい全てのフィールド */ })
// または、取得したくないフィールドを 0 で指定する方が簡単な場合もある
// db.users.find({}, { address: 0, hobbies: 0 }) // address と hobbies 以外を取得
注意: 1と0を混在させることはできません(_idフィールドを除く)。例えば、{ name: 1, age: 0 } のような指定はエラーになります。
検索のまとめ
find()メソッドは非常に強力で、様々な条件を組み合わせて柔軟な検索が可能です。クエリセレクタ、比較演算子、論理演算子、要素演算子、配列演算子、ネストされたドキュメントへのアクセス、正規表現、そしてカーソルメソッド(limit, skip, sort, projection)を組み合わせることで、ほとんどの検索要件を満たすことができます。
大量データに対して効率的に検索を行うためには、適切なインデックスの設定が非常に重要になります。これについては後述します。
3. ドキュメントの更新 (Update)
既存のドキュメントの内容を変更します。単一のドキュメントを更新するupdateOne()と、複数のドキュメントを更新するupdateMany()があります。また、条件に一致するドキュメントが存在しない場合に新しく挿入するreplaceOne()や、upsertオプションもあります。
基本的な構文は db.collection.updateOne(query, update, options) または db.collection.updateMany(query, update, options) です。
query(必須): 更新するドキュメントを特定するための条件です。update(必須): ドキュメントをどのように変更するかを指定します。通常、更新演算子を使います。options(任意): 更新の挙動を制御するオプションです(例:upsert)。
updateOne(): 単一ドキュメントの更新
条件に一致するドキュメントのうち、最初の1件のみを更新します。
例:
nameが “山田 太郎” のドキュメントを見つけて、cityを “大阪府” に変更する:
javascript
db.users.updateOne(
{ name: "山田 太郎" }, // 検索条件 (クエリ)
{ $set: { city: "大阪府" } } // 更新内容 (更新演算子 $set を使用)
)
成功すると、以下のような結果が返されます。
json
{
acknowledged: true,
insertedId: null, // upsert しない限り null
matchedCount: 1, // 検索条件に一致したドキュメント数
modifiedCount: 1, // 実際に更新されたドキュメント数
upsertedId: null // upsert しない限り null
}
updateMany(): 複数ドキュメントの更新
条件に一致する全てのドキュメントを更新します。
例:
ageが 30 以上の全てのドキュメントに、statusフィールドを"senior"として追加/更新する:
javascript
db.users.updateMany(
{ age: { $gte: 30 } }, // 検索条件
{ $set: { status: "senior" } } // 更新内容
)
成功すると、以下のような結果が返されます(一致するドキュメントが2件、両方更新された場合)。
json
{
acknowledged: true,
insertedId: null,
matchedCount: 2,
modifiedCount: 2,
upsertedId: null
}
更新演算子
updateドキュメントでは、どのようにドキュメントを変更するかを指示するために、特別な「更新演算子」を使います。これがMongoDBの更新操作の強力な特徴の一つです。
-
$set: フィールドの値を設定します。フィールドが存在しない場合は新しく追加します。
javascript
// name が "鈴木 花子" のドキュメントの email を設定/更新
db.users.updateOne({ name: "鈴木 花子" }, { $set: { email: "[email protected]" } })
// 新しいフィールド occupation を追加
db.users.updateOne({ name: "山田 太郎" }, { $set: { occupation: "Engineer" } })
// 複数のフィールドを一度に設定
db.users.updateOne({ name: "山田 太郎" }, { $set: { city: "京都市", age: 31 } })
// ネストされたフィールドを設定
db.users.updateOne({ name: "山田 太郎" }, { $set: { "address.zip": "600-0000" } }) -
$unset: 指定したフィールドをドキュメントから削除します。値は何でも構いません(通常は""や1を指定します)。
javascript
// name が "佐藤 健太" のドキュメントから email フィールドを削除
db.users.updateOne({ name: "佐藤 健太" }, { $unset: { email: "" } }) -
$inc: 数値フィールドの値を増減させます。指定した数値分だけ値を加算します。フィールドが存在しない場合は0として扱われ、指定した値が設定されます。
javascript
// name が "山田 太郎" のドキュメントの age を 1 増やす
db.users.updateOne({ name: "山田 太郎" }, { $inc: { age: 1 } })
// name が "山田 太郎" のドキュメントの age を 5 減らす
db.users.updateOne({ name: "山田 太郎" }, { $inc: { age: -5 } }) -
$push: 配列フィールドに要素を追加します。通常、配列の末尾に追加されます。配列フィールドが存在しない場合は、新しい配列として作成されます。
javascript
// name が "山田 太郎" のドキュメントの hobbies 配列に "料理" を追加
db.users.updateOne({ name: "山田 太郎" }, { $push: { hobbies: "料理" } })
// 複数の要素を一度に追加することも可能 ($each オペレーターと組み合わせる)
db.users.updateOne({ name: "山田 太郎" }, { $push: { hobbies: { $each: ["映画", "ゲーム"] } } })
// 配列フィールドにネストされたドキュメントを追加
db.users.updateOne({ name: "山田 太郎" }, { $push: { past_cities: { city: "札幌", year: 2010 } } }) -
$addToSet: 配列フィールドに要素を追加します。ただし、その要素が既に配列内に存在しない場合のみ追加します。重複を避けたい場合に便利です。
javascript
// name が "山田 太郎" のドキュメントの hobbies 配列に "読書" を追加(既に存在するので追加されない)
db.users.updateOne({ name: "山田 太郎" }, { $addToSet: { hobbies: "読書" } })
// hobbies 配列に "絵画" を追加(存在しないので追加される)
db.users.updateOne({ name: "山田 太郎" }, { $addToSet: { hobbies: "絵画" } }) -
$pull: 配列フィールドから、指定した条件に一致する全ての要素を削除します。
javascript
// name が "山田 太郎" のドキュメントの hobbies 配列から "旅行" を削除
db.users.updateOne({ name: "山田 太郎" }, { $pull: { hobbies: "旅行" } })
// past_cities 配列から、city が "札幌" の要素を全て削除
db.users.updateOne({ name: "山田 太郎" }, { $pull: { past_cities: { city: "札幌" } } }) -
$pop: 配列フィールドの最初 (-1) または最後 (1) の要素を削除します。
javascript
// name が "山田 太郎" のドキュメントの hobbies 配列の最後の要素を削除
db.users.updateOne({ name: "山田 太郎" }, { $pop: { hobbies: 1 } })
// hobbies 配列の最初の要素を削除
db.users.updateOne({ name: "山田 太郎" }, { $pop: { hobbies: -1 } }) -
$rename: フィールドの名前を変更します。
javascript
// name が "山田 太郎" のドキュメントの city フィールド名を prefecture に変更
db.users.updateOne({ name: "山田 太郎" }, { $rename: { city: "prefecture" } })
これらの更新演算子を組み合わせることで、ドキュメント内のデータを非常に柔軟に変更できます。
upsert オプション
updateMany()やupdateOne()の第3引数であるoptionsドキュメントに { upsert: true } を指定すると、queryに一致するドキュメントが見つからなかった場合に、新しいドキュメントが挿入されます。この新しいドキュメントは、queryドキュメントとupdateドキュメントをマージして作成されます($setOnInsert演算子なども利用可能)。
例:
nameが “田中 一郎” のドキュメントを探す。もしあればageを 40 に設定する。もしなければ、name:”田中 一郎”、age:40、city:”福岡県” の新しいドキュメントを挿入する。
javascript
db.users.updateOne(
{ name: "田中 一郎" },
{ $set: { age: 40 }, $setOnInsert: { city: "福岡県" } }, // $setOnInsert は upsert 時にのみ適用
{ upsert: true } // upsert を有効化
)
$setOnInsert演算子は、upsertによって新しいドキュメントが挿入される場合にのみフィールドを設定します。これにより、更新時と挿入時で異なるデフォルト値を設定するなどが可能になります。
replaceOne(): ドキュメントの置き換え
replaceOne(query, replacement, options)メソッドは、条件に一致する最初の1件のドキュメントを、指定したreplacementドキュメントで完全に置き換えます(_idフィールドは除く)。更新演算子を使うのではなく、ドキュメント全体を新しいもので上書きしたい場合に利用します。
例:
nameが “鈴木 花子” のドキュメントを見つけて、その内容を以下のドキュメントで置き換える:
javascript
db.users.replaceOne(
{ name: "鈴木 花子" }, // 検索条件
{
name: "佐藤 由美", // name も変更
age: 28,
city: "北海道",
status: "active" // 既存のフィールドは全て消え、新しいフィールドになる
}
)
元の “鈴木 花子” ドキュメントにあったemailやisActiveフィールドは削除され、新しいstatusフィールドが追加されます。_idは元のドキュメントのものが維持されます。
replaceOneにもupsertオプションがあります。
4. ドキュメントの削除 (Delete)
コレクションからドキュメントを削除します。単一のドキュメントを削除するdeleteOne()と、複数のドキュメントを削除するdeleteMany()があります。
基本的な構文は db.collection.deleteOne(query) または db.collection.deleteMany(query) です。
query(必須): 削除するドキュメントを特定するための条件です。
deleteOne(): 単一ドキュメントの削除
条件に一致するドキュメントのうち、最初の1件のみを削除します。
例:
nameが “田中 一郎” のドキュメントを1件削除する:
javascript
db.users.deleteOne({ name: "田中 一郎" })
成功すると、以下のような結果が返されます。
json
{ acknowledged: true, deletedCount: 1 } // 削除されたドキュメント数
deleteMany(): 複数ドキュメントの削除
条件に一致する全てのドキュメントを削除します。
例:
ageが 35 以上の全てのドキュメントを削除する:
javascript
db.users.deleteMany({ age: { $gte: 35 } })
成功すると、以下のような結果が返されます(一致するドキュメントが複数あり、全て削除された場合)。
json
{ acknowledged: true, deletedCount: 2 }
全てのドキュメントを削除:
deleteMany()のクエリを空のドキュメント {} にすることで、コレクション内の全てのドキュメントを削除できます。これは注意して実行してください。
javascript
db.users.deleteMany({}) // users コレクションの全てのドキュメントを削除
コレクション全体の削除 (drop())
コレクション内の全てのドキュメントを削除するだけでなく、コレクション自体を完全に削除したい場合は、コレクションオブジェクトに対してdrop()メソッドを使います。
javascript
db.users.drop() // users コレクションと全てのドキュメントを削除
成功すると true が返されます。
データベース全体の削除 (dropDatabase())
現在選択しているデータベース全体を削除したい場合は、dbオブジェクトに対してdropDatabase()メソッドを使います。これはデータベース内の全てのコレクションとドキュメントを削除します。非常に強力なコマンドなので、実行する際は十分注意してください。
まず、削除したいデータベースを選択します。
javascript
use mydatabase // 削除したいデータベースを選択
db.dropDatabase() // 現在選択しているデータベースを削除
成功すると、削除されたデータベース名を含む結果が返されます。
json
{ "dropped": "mydatabase", "ok": 1 }
データベースを削除すると、シェルはデフォルトのtestデータベースに戻ることがあります。
これでMongoDBのCRUD操作の基本的なコマンドを網羅しました。これらのコマンドを使いこなせれば、データの作成、検索、更新、削除といった基本的なデータベース操作は問題なく行えるようになります。
さらにステップアップ:インデックスとアグリゲーション
CRUD操作はデータベース操作の基本ですが、MongoDBにはさらに高度な機能があります。ここでは、データの検索パフォーマンスを向上させるための「インデックス」と、複雑なデータ集計を行うための「アグリゲーションフレームワーク」の基本を紹介します。
インデックス (Indexes)
インデックスは、特定のフィールドやフィールド群に基づいてデータを効率的に検索するための特別なデータ構造です。RDBのインデックスと同様の概念です。インデックスを設定することで、クエリの実行速度を劇的に向上させることができます。特に、find()メソッドのクエリ条件 (query) や、sort()メソッドで使用するフィールドにインデックスを設定することが効果的です。
インデックスがない場合、MongoDBはコレクション内の全てのドキュメントをスキャンして検索条件に一致するかを確認します(これを「コレクションスキャン」と呼びます)。これはデータ量が多い場合に非常に時間がかかります。インデックスがあれば、MongoDBはインデックスを使って効率的に目的のドキュメントを見つけ出すことができます。
インデックスの作成 (createIndex())
インデックスはcreateIndex()メソッドで作成します。構文は db.collection.createIndex(key, options) です。
key(必須): インデックスを作成するフィールドとソート順を指定するドキュメントです。{ field: 1 }で昇順インデックス、{ field: -1 }で降順インデックスを作成します。複数のフィールドを指定することで複合インデックスを作成できます。options(任意): インデックスのオプションを指定します(例:unique,partialFilterExpression,backgroundなど)。
例:
usersコレクションのnameフィールドに昇順インデックスを作成する:
javascript
db.users.createIndex({ name: 1 })productsコレクションのpriceフィールドに降順インデックスを作成する:
javascript
db.products.createIndex({ price: -1 })-
usersコレクションのcityフィールドとageフィールドに複合インデックスを作成する(まずcityで昇順、次にageで降順):
javascript
db.users.createIndex({ city: 1, age: -1 })
複合インデックスは、クエリがインデックスのプレフィックス(例:{ city: "東京都" }または{ city: "東京都", age: { $gt: 30 } })を含む場合に有効です。 -
emailフィールドにユニークインデックスを作成する(同じemailを持つドキュメントの挿入を禁止する):
javascript
db.users.createIndex({ email: 1 }, { unique: true }) - 部分インデックス(条件に一致するドキュメントにのみインデックスを作成することで、インデックスサイズを小さくする):
statusが “active” のドキュメントのemailフィールドにインデックスを作成する。
javascript
db.users.createIndex({ email: 1 }, { partialFilterExpression: { status: "active" } })
インデックスの作成は、コレクション内のドキュメント数が多い場合、時間とシステムリソースを消費する可能性があります。デフォルトではフォアグラウンドで実行されますが、運用中のシステムでは{ background: true }オプションを付けてバックグラウンドで実行することが推奨されます。
インデックスの確認 (getIndexes())
コレクションに作成されているインデックスの一覧を確認するには、getIndexes()メソッドを使います。
javascript
db.users.getIndexes()
実行すると、_idフィールドに自動的に作成されるデフォルトのインデックスと、自分で作成したインデックスのリストが表示されます。
インデックスの削除 (dropIndex(), dropIndexes())
不要になったインデックスを削除するには、dropIndex()またはdropIndexes()メソッドを使います。
dropIndex(indexName): 指定した名前のインデックスを削除します。インデックス名はgetIndexes()で確認できます(通常は作成時に指定したフィールド名や複合フィールド名から自動生成されますが、作成オプションで名前を指定することも可能です)。dropIndex(key): インデックス作成時に使用したkeyドキュメントを指定して削除することもできます。dropIndexes():_idインデックス以外の全てのインデックスを削除します。
例:
nameフィールドのインデックスを削除する (インデックス名が name_1 の場合):
javascript
db.users.dropIndex("name_1")cityとageの複合インデックスを削除する (keyを指定):
javascript
db.users.dropIndex({ city: 1, age: -1 })_id以外の全てのインデックスを削除する:
javascript
db.users.dropIndexes()
インデックスは検索パフォーマンスを向上させますが、ドキュメントの挿入、更新、削除の際にインデックスも更新する必要があるため、書き込みパフォーマンスにはわずかなオーバーヘッドが発生します。また、インデックスはディスク容量も消費します。闇雲に多くのインデックスを作成するのではなく、よく使うクエリやソート条件に合わせて適切なインデックスを作成することが重要です。
アグリゲーションフレームワーク (Aggregation Framework)
find()メソッドは条件に一致するドキュメントを取得するのに使いますが、複数のドキュメントをまとめて集計したり、データの形を変えたり、結合したりといった複雑な処理を行うには、アグリゲーションフレームワークを使います。
アグリゲーションフレームワークは、複数の「ステージ」をパイプラインのように連結して処理を行います。あるステージの出力が次のステージの入力となり、最終的に集計結果が得られます。
基本的な構文は db.collection.aggregate([ stage1, stage2, stage3, ... ]) です。
アグリゲーションフレームワークには多数のステージがありますが、ここではよく使われる基本的なステージをいくつか紹介します。
$match: 入力ドキュメントをフィルタリングします。find()のクエリ条件とほぼ同じ記述ができます。通常、パイプラインの最初の方に置いて、処理対象のドキュメント数を減らすために使います。$group: 入力ドキュメントを指定したキーでグループ化し、各グループに対して集計関数(例:sum,avg,countなど)を適用して集計値を計算します。$project: 入力ドキュメントのフィールドを整形したり、新しいフィールドを追加したり、既存のフィールドの名前を変更/削除したりします。取得するフィールドを指定するprojection()と似ていますが、より強力です。$sort: 入力ドキュメントを指定したフィールドでソートします。$limit: 入力ドキュメントを先頭から指定した数に制限します。$skip: 入力ドキュメントを先頭から指定した数だけスキップします。
例:
ordersコレクションに以下のドキュメントがあるとする。
json
{ "_id": 1, "product": "A", "price": 10, "quantity": 2, "user": "Alice", "date": ISODate("2023-10-26T10:00:00Z") }
{ "_id": 2, "product": "B", "price": 20, "quantity": 1, "user": "Bob", "date": ISODate("2023-10-26T11:00:00Z") }
{ "_id": 3, "product": "A", "price": 10, "quantity": 3, "user": "Alice", "date": ISODate("2023-10-27T10:00:00Z") }
{ "_id": 4, "product": "C", "price": 30, "quantity": 1, "user": "Alice", "date": ISODate("2023-10-27T11:00:00Z") }
{ "_id": 5, "product": "B", "price": 20, "quantity": 2, "user": "Bob", "date": ISODate("2023-10-28T10:00:00Z") }
-
ユーザーごとの合計購入金額を計算する:
javascript
db.orders.aggregate([
{ // ステージ 1: 各注文の合計金額を計算して total フィールドに追加
$project: {
user: 1,
total: { $multiply: ["$price", "$quantity"] } // price * quantity を計算
}
},
{ // ステージ 2: user でグループ化し、各グループの total を合計
$group: {
_id: "$user", // user フィールドの値でグループ化
totalAmount: { $sum: "$total" } // グループ内の total フィールドの合計を計算し totalAmount というフィールド名にする
}
},
{ // ステージ 3: 合計金額の降順でソート
$sort: {
totalAmount: -1
}
}
])
実行結果例:
json
[
{ "_id": "Alice", "totalAmount": 80 }, // (10*2 + 10*3 + 30*1) = 20 + 30 + 30 = 80
{ "_id": "Bob", "totalAmount": 60 } // (20*1 + 20*2) = 20 + 40 = 60
] -
2023年10月27日の注文のみを抽出し、製品ごとに合計数量と合計金額を計算する:
javascript
db.orders.aggregate([
{ // ステージ 1: 2023年10月27日の注文のみをフィルタリング
$match: {
date: {
$gte: ISODate("2023-10-27T00:00:00.000Z"),
$lt: ISODate("2023-10-28T00:00:00.000Z")
}
}
},
{ // ステージ 2: product でグループ化し、数量と金額を合計
$group: {
_id: "$product", // product フィールドの値でグループ化
totalQuantity: { $sum: "$quantity" }, // 数量の合計
totalAmount: { $sum: { $multiply: ["$price", "$quantity"] } } // 合計金額
}
},
{ // ステージ 3: 結果を整形(_id を product フィールド名に変更)
$project: {
_id: 0, // デフォルトの _id フィールドを削除
product: "$_id", // グループキーである _id の値を product フィールドにコピー
totalQuantity: 1,
totalAmount: 1
}
},
{ // ステージ 4: 製品名の昇順でソート
$sort: {
product: 1
}
}
])
実行結果例(27日の注文のみ対象: _id 3, 4):
json
[
{ "product": "A", "totalQuantity": 3, "totalAmount": 30 }, // product A: quantity 3, price 10 -> total 30
{ "product": "C", "totalQuantity": 1, "totalAmount": 30 } // product C: quantity 1, price 30 -> total 30
]
アグリゲーションフレームワークは非常に強力で、これらの他にも様々なステージ(例: $lookupによるコレクション結合、$unwindによる配列展開、$outによる結果の別コレクションへの出力など)があります。複雑なレポート作成やデータ変換が必要な場合に不可欠な機能です。
発展的なトピック:トランザクションの基本
データベース操作において、複数の操作を一つのまとまった単位として扱い、「全て成功するか、全て失敗する(元に戻る)」ようにしたい場合があります。このような要件を満たすのが「トランザクション」です。例えば、銀行口座間の送金処理では、「A口座から引き落とし」と「B口座へ入金」という2つの操作を、どちらか一方だけが成功するという状況は避けなければなりません。
MongoDBは、バージョン4.0からレプリカセット環境で、バージョン4.2からシャードクラスター環境でマルチドキュメントトランザクションをサポートしています。これにより、複数のドキュメントやコレクションにまたがる操作でもアトミック性(不可分性)を保証できるようになりました。
トランザクションは、以下の手順で実行します。
- セッションを開始する (
startSession()) - トランザクションを開始する (
startTransaction()) - トランザクション内でデータベース操作(CRUD)を実行する
- 全ての操作が成功したら、トランザクションをコミットする (
commitTransaction()) - 途中でエラーが発生したら、トランザクションをアボートする(ロールバックする) (
abortTransaction())
例: ユーザーAからユーザーBへポイントを移動する(擬似的な例)
“`javascript
// セッションを開始
const session = db.getMongo().startSession();
try {
// トランザクションを開始
session.startTransaction();
// トランザクション内でデータベース操作を実行
// ユーザーAのポイントを減らす
session.getDatabase(“mydatabase”).collection(“users”).updateOne(
{ name: “ユーザーA” },
{ $inc: { points: -100 } },
{ session: session } // 必ず session オプションを指定
);
// ユーザーBのポイントを増やす
session.getDatabase(“mydatabase”).collection(“users”).updateOne(
{ name: “ユーザーB” },
{ $inc: { points: 100 } },
{ session: session } // 必ず session オプションを指定
);
// 全ての操作が成功したらコミット
session.commitTransaction();
print(“Transaction committed.”);
} catch (error) {
// エラーが発生したらアボート
session.abortTransaction();
print(“Transaction aborted due to error:”, error);
} finally {
// セッションを終了
session.endSession();
}
“`
トランザクション内の各データベース操作メソッド(updateOne, insertMany, deleteOneなど)には、必ず { session: session } オプションを指定する必要があります。
トランザクションは複雑なデータ操作においてデータの整合性を保つために非常に重要ですが、パフォーマンスへの影響や制約もあるため、必要に応じて慎重に設計・使用する必要があります。入門段階では、まずは基本的なCRUD操作とアグリゲーションをしっかり理解することに重点を置くのが良いでしょう。
実践的なヒントとベストプラクティス
最後に、MongoDBをより効果的に使うための実践的なヒントをいくつか紹介します。
ドキュメントスキーマ設計の考え方
RDBのように厳格なスキーマ定義がないMongoDBですが、データの構造をどのように設計するか(スキーマ設計)は非常に重要です。主な設計パターンとして「エンベデッド(埋め込み)」と「リファレンス(参照)」があります。
-
エンベデッド (Embedded): 関連するデータを親ドキュメントの中に埋め込む方法です。
- 利点: 読み込み操作が簡単(JOIN不要)、一度の読み込みで関連データも取得できる。
- 欠点: 親ドキュメントが大きくなりすぎる可能性がある、埋め込んだデータの更新が親ドキュメント全体の更新になる、埋め込んだデータに対して単独でクエリやインデックスを作成するのが難しい場合がある。
- 向いているケース: 1対1または1対少ない数の関係性で、頻繁に一緒に読み込まれるデータ。例: ユーザー情報に住所を埋め込む、注文ドキュメントに購入した商品のリストを埋め込む。
-
リファレンス (Reference): 関連するデータを別のコレクションに置き、親ドキュメントに子ドキュメントの
_idなどの参照情報を保持する方法です。RDBの外部キーに似ています。- 利点: ドキュメントサイズが小さく保てる、独立したクエリやインデックス作成が容易、多対多の関係性を表現しやすい。
- 欠点: 関連データを取得するために追加のクエリが必要になる(アグリゲーションの
$lookupステージなど)。 - 向いているケース: 1対多または多対多の関係性で、関連データが独立して頻繁に操作される、関連データの数が非常に多い可能性がある。例: ブログ記事とコメント、商品とレビュー。
どちらのパターンが良いかは、アプリケーションのアクセスパターン(どのデータを一緒に読み込むことが多いか、どのデータを独立して更新するかなど)によって異なります。MongoDBではこれらを組み合わせて使うのが一般的です。
ObjectIdについて
_idフィールドのデフォルト値であるObjectIdは単なるランダムなIDではありません。12バイトの構造を持っており、以下の情報を含んでいます。
- 4バイトのタイムスタンプ(Unix時間)
- 3バイトのマシンID
- 2バイトのプロセスID
- 3バイトのカウンター
これにより、ObjectIdが生成されたおおよその時刻を知ることができます。また、単一のマシン上で複数のプロセスから同時に生成されても、競合しにくいようになっています。
ObjectIdは生成時刻順に増加するため、_idフィールドに対するデフォルトのインデックス (_id: 1) は、データの挿入順に並べたり、最新のデータを取得したりする際に非常に効率的です。
パフォーマンスに関する考慮事項
- インデックス: 前述の通り、よく使うクエリやソート条件には必ず適切なインデックスを作成してください。
explain()メソッドを使うと、クエリがどのインデックスを使っているか、コレクションスキャンになっているかなどを確認できます。 - クエリの最適化: 不要なフィールドを取得しない (
projection)、条件を絞り込む ($matchをパイプラインの最初に置く)、正規表現クエリはパフォーマンスに影響しやすいなどの点に注意してください。 - Read Preference: レプリカセット環境では、読み込み操作をどのノード(プライマリかセカンダリか)に送るかを制御することで、読み込みスループットを向上させることができます。
- Write Concern: 書き込み操作がどれだけの数のノードに複製されたら成功とみなすかを指定できます。データの耐久性と書き込みパフォーマンスのトレードオフになります。
セキュリティの基本
運用環境では、セキュリティ対策が必須です。
- 認証: ユーザー名とパスワードによる認証を有効にしてください。デフォルトでは認証なしで誰でも接続できてしまいます。
- 認可: 各ユーザーがどのデータベース、どのコレクション、どの操作(読み込み、書き込みなど)を実行できるかを制御するロールベースアクセス制御(RBAC)を設定してください。
- ネットワーク設定: MongoDBサーバーは、信頼できるクライアントからの接続のみを許可するようにファイアウォールなどを設定してください。外部からの不必要なアクセスをブロックします。
- TLS/SSL: クライアントとサーバー間の通信を暗号化することで、データの盗聴を防ぎます。
これらのセキュリティ対策は、MongoDBを安全に運用するために非常に重要です。
まとめ:MongoDBコマンド習得の第一歩を踏み出して
この記事では、MongoDBの基本的な概念から始まり、ドキュメントの作成、読み込み、更新、削除といったCRUD操作の基本コマンド、さらにパフォーマンス向上のためのインデックス、複雑な集計のためのアグリゲーションフレームワーク、そして複数操作のアトミック性を保証するトランザクションの基本までを、約5000語にわたって詳細に解説しました。
- MongoDBはドキュメント、コレクション、データベースという単位でデータを管理します。
- ドキュメントは柔軟な構造を持つBSON形式のデータです。
mongoまたはmongoshシェルを使ってコマンドを実行します。use、show dbs、show collectionsでデータベースやコレクションを操作します。- データのCRUD操作は、コレクションオブジェクトに対して行います (
db.collection.insertOne(),find(),updateOne(),deleteOne()など)。 find()メソッドは、多様なクエリセレクタや演算子、そしてカーソルメソッドを組み合わせて強力な検索が可能です。- 更新操作では
$set,$inc,$push,$pullなどの更新演算子を使いこなすことが重要です。 - パフォーマンス向上のためには、適切なフィールドにインデックスを作成することが不可欠です。
- 複雑な集計やデータ変換にはアグリゲーションフレームワークが役立ちます。
- 複数操作のアトミック性が必要な場合はトランザクションを使用します(レプリカセット/シャードクラスター環境が必要)。
この記事で紹介したコマンドは、MongoDBを使う上での基礎中の基礎です。しかし、これらの基本をしっかりと理解し、実際に手を動かしてコマンドを実行してみることが、MongoDBを習得する上で最も重要なステップです。
最初は少し難しく感じるかもしれませんが、各コマンドの構文と、どのような時にどのコマンドやオプションを使うのかを繰り返し練習することで、自然と身についていきます。公式ドキュメントにはさらに詳しい情報や、今回紹介しきれなかった多くの機能(レプリケーション、シャーディング、各種ストレージエンジンなど)についての説明がありますので、次に進むステップとして参考にしてください。
さあ、MongoDBの世界へ飛び込み、あなたのアプリケーション開発やデータ管理に活かしていきましょう!この記事が、あなたのMongoDB学習の確かな一歩となることを願っています。