MongoDB をゼロから始める入門


MongoDBをゼロから始める徹底入門:ドキュメント指向データベースの基礎から実践まで

はじめに:なぜ今、MongoDBを学ぶのか?

現代のウェブアプリケーション開発において、データベースは不可欠な要素です。従来、データベースといえばリレーショナルデータベース(RDB)が主流であり、MySQLやPostgreSQL、Oracleといった製品が広く使われてきました。しかし、インターネット技術の進化、ビッグデータの登場、そしてマイクロサービスやアジャイル開発といった開発手法の普及に伴い、RDBだけでは対応しきれないケースが増えてきました。

そこで注目されているのが「NoSQL(Not only SQL)」データベースです。NoSQLデータベースは、リレーショナルモデルにとらわれず、データの保存や取得に多様な方法を提供するデータベースの総称です。その中でも、柔軟性、スケーラビリティ、開発の容易さから特に人気を集めているのが、ドキュメント指向データベースであるMongoDBです。

本記事は、「データベースに触れるのが初めて」「RDBは使ったことがあるが、NoSQLは未経験」「MongoDBに興味があるけれど、何から始めれば良いかわからない」という方を対象に、MongoDBの基本的な概念から、データの操作、そして少し応用的な機能までを、約5000語の詳細な解説と豊富なコード例を交えて徹底的に解説します。

この記事を読み終える頃には、MongoDBの基本的な使い方をマスターし、自分のプロジェクトでMongoDBを活用するための第一歩を踏み出せるようになっているでしょう。さあ、MongoDBの世界への旅を始めましょう!

1. MongoDBとは何か? NoSQLとドキュメント指向データベース

まずは、MongoDBがどのようなデータベースなのかを理解することから始めます。

1.1 NoSQLデータベースとは?

NoSQLは「Not only SQL」の略称であり、「SQLだけではない」データベースを指します。RDBが厳格なスキーマを持つテーブルと行の関係性でデータを管理するのに対し、NoSQLデータベースはより柔軟なデータ構造を持ち、様々な特性に特化しています。

NoSQLデータベースは、大きく以下の4つの種類に分類できます。

  • キーバリュー型 (Key-Value Store): キーと値のペアでデータを格納します。非常に単純で高速な読み書きが得意ですが、複雑なクエリには向いていません。(例: Redis, Amazon DynamoDB)
  • カラム指向型 (Column-Family Store): データを列(カラム)のファミリー単位で管理します。大量データの高速な書き込みや特定の列に対するクエリが得意です。(例: Apache Cassandra, HBase)
  • ドキュメント指向型 (Document Store): データを「ドキュメント」という単位で管理します。ドキュメントは自己記述的であり、構造の変更が容易です。(例: MongoDB, Couchbase)
  • グラフ型 (Graph Database): データ間の関係性(エッジ)とデータそのもの(ノード)をグラフ構造で管理します。複雑な関係性のクエリが得意です。(例: Neo4j, Amazon Neptune)

MongoDBは、この中のドキュメント指向型データベースに分類されます。

1.2 ドキュメント指向データベースの特徴

ドキュメント指向データベースは、データを「ドキュメント」という単位で扱います。MongoDBでは、このドキュメントの形式として主にBSON (Binary JSON) を使用します。JSON (JavaScript Object Notation) は、人間にとって読み書きしやすいデータ形式ですが、BSONはそれをコンピュータが効率的に処理できるようバイナリ化したものです。BSONはJSONで表現できる構造をすべて表現できるだけでなく、日付やバイナリデータなど、JSONにはないデータ型もサポートしています。

ドキュメント指向データベースの大きな特徴は以下の通りです。

  • スキーマレス(Schema-less)またはスキーマフレキシブル(Schema-flexible): RDBのように事前に厳格なスキーマ(テーブル定義)を定義する必要がありません。同じコレクション内のドキュメントであっても、異なるフィールドを持つことができます。これにより、開発の初期段階や、データ構造が頻繁に変化するようなアジャイル開発に適しています。ただし、完全にスキーマがないわけではなく、アプリケーション側でデータの構造を扱う必要があります。
  • 自己記述的なドキュメント: ドキュメント自体がデータの構造(フィールドとその値)を含んでいます。これにより、アプリケーションはドキュメントを読み込むだけでデータの意味を理解できます。
  • 階層構造の表現: ドキュメント内に他のドキュメントや配列を埋め込むことで、複雑な階層構造を持つデータを自然な形で表現できます。これは、RDBでJOINを使って複数のテーブルを結合しなければ取得できないような関連データを、1つのドキュメントとして保持できることを意味します。

1.3 MongoDBの主要な特徴

ドキュメント指向であることに加えて、MongoDBは以下のような強力な特徴を持っています。

  • 柔軟性: ドキュメント構造の柔軟性により、データの変更や新しいフィールドの追加が容易です。
  • 豊富なクエリ機能: ドキュメント内のフィールドに対する多様なクエリ(検索)機能を持ちます。比較、論理演算、正規表現、地理空間クエリなど、様々な条件でデータを検索できます。
  • インデックス: クエリのパフォーマンスを向上させるために、様々な種類のインデックスを作成できます。
  • アグリゲーションフレームワーク (Aggregation Framework): データの集計、変換、分析を行うための強力なフレームワークを提供します。RDBのGROUP BYや集計関数、ビューのような機能を実現できます。
  • 高可用性 (High Availability): レプリカセット (Replica Set) と呼ばれる仕組みにより、データの複製を複数のサーバーに保持し、サーバー障害が発生してもサービスを継続できます。
  • 水平スケーラビリティ (Horizontal Scalability): シャーディング (Sharding) と呼ばれる仕組みにより、大量のデータを複数のサーバー(シャード)に分散させ、データ量やアクセス負荷の増加に対応できます。
  • 多様なドライバー: 様々なプログラミング言語(Java, Python, Node.js, Ruby, PHP, C#, Goなど)向けの公式ドライバーが提供されており、アプリケーションからのアクセスが容易です。

1.4 RDBとの比較

MongoDBを理解する上で、RDBとの違いを把握することは非常に役立ちます。主要な概念を比較してみましょう。

RDBの概念 MongoDBの概念 説明
データベース データベース データの集合を管理する最上位の論理的なコンテナ
テーブル コレクション ドキュメントの集合。RDBのテーブルに相当するが、スキーマは柔軟
行 (Row) ドキュメント データの単一のレコード。RDBの行に相当するが、自己記述的で階層を持つ
列 (Column) フィールド ドキュメント内のキーと値のペアのキー。RDBの列に相当する
スキーマ スキーマレス/スキーマフレキシブル RDBは厳格な事前定義が必要。MongoDBは柔軟。
JOIN 組み込み (Embedded) / 参照 (Reference) 関連データを1つのドキュメントに含めるか、別のドキュメントを参照する

RDBでは、異なる種類のデータは通常、正規化によって複数のテーブルに分割され、必要に応じてJOINを使って結合されます。一方MongoDBでは、関連性の高いデータは1つのドキュメントに組み込み(埋め込み)として格納することが多いです。これにより、JOINのような結合処理が不要になり、データの読み込みパフォーマンスが向上する場合があります。ただし、データが重複したり、ドキュメントが巨大化したりする可能性もあるため、状況に応じたデータモデリングが必要です(これは後述します)。

2. MongoDBのインストールとセットアップ

MongoDBを実際に使い始めるためには、まずインストールが必要です。ここでは、ローカル環境にMongoDB Community Serverをインストールし、基本的なツールを使えるようにする手順を説明します。また、クラウドサービスであるMongoDB Atlasについても簡単に紹介します。

2.1 MongoDB Community Serverのダウンロードとインストール

MongoDBには商用版のEnterprise版と無償のCommunity版があります。個人学習や小規模なプロジェクトであればCommunity版で十分です。

公式サイト(https://www.mongodb.com/try/download/community)から、お使いのOS(Windows, macOS, Linux)に合ったインストーラーをダウンロードしてください。

  • Windows: MSIインストーラーをダウンロードし、ウィザードに従ってインストールします。インストール時に「Install MongoDB Compass」にチェックを入れておくと、GUIツールも同時にインストールできて便利です。カスタムインストールを選択した場合、インストールディレクトリやデータディレクトリ、ログディレクトリを指定できます。デフォルトでサービスとして起動するように設定するのが一般的です。
  • macOS: Homebrewを使うのが簡単です。ターミナルを開き、brew tap mongodb/brewを実行した後、brew install mongodb-communityを実行します。サービスの起動/停止はbrew services start mongodb-community / brew services stop mongodb-communityで行います。
  • Linux: 各ディストリビューション(Ubuntu, Debian, CentOS, RHELなど)向けのパッケージマネージャー(apt, yumなど)を使ったインストール手順が公式サイトに詳しく記載されています。お使いの環境に合わせて手順を確認してください。一般的にはリポジトリを追加してからパッケージをインストールします。

インストール後、MongoDBサーバー(mongodプロセス)が起動していることを確認してください。

2.2 MongoDB Compass (GUIツール) のインストール

MongoDB Compassは、MongoDBのデータを視覚的に操作できる公式のGUIツールです。データの閲覧、挿入、編集、削除、コレクションの管理、クエリの実行、インデックスの作成などを直感的に行えます。

もしMongoDB Community Serverのインストール時にCompassを含めなかった場合は、公式サイト(https://www.mongodb.com/try/download/compass)から別途ダウンロードしてインストールできます。

インストール後、Compassを起動すると、接続設定画面が表示されます。デフォルトではmongodb://localhost:27017/というURIが入力されているはずです。これがローカルでデフォルトポート(27017)で起動しているMongoDBサーバーへの接続設定です。「Connect」ボタンをクリックすれば、接続できます。

2.3 MongoDB Shell (mongosh) の基本的な使い方

mongosh(旧mongoシェル)は、MongoDBサーバーと対話するためのコマンドラインツールです。サーバーのインストール時に一緒にインストールされることが多いですが、別途インストールすることも可能です(公式サイトを参照)。

ターミナルまたはコマンドプロンプトを開き、mongoshと入力してEnterキーを押すと、mongoshが起動します。

bash
$ mongosh

Current Mongosh version: ...
Using Node.js version: ...
Using AMD built-in colors: ...
...
test>

test>というプロンプトが表示されれば成功です。これは現在testという名前のデータベースに接続していることを意味します。

基本的なコマンド:

  • show dbs; または show databases; : サーバー上のデータベース一覧を表示します。
  • use <database_name>; : 指定したデータベースに切り替えます。もし存在しないデータベース名を指定した場合、その名前で新しいデータベースが作成されます(ただし、コレクションに初めてデータを挿入するまで、物理的なデータベースファイルは作成されません)。
    javascript
    test> use myappdb;
    switched to db myappdb
    myappdb>
  • db; : 現在接続しているデータベース名を表示します。
  • show collections; : 現在のデータベース内のコレクション一覧を表示します。

mongoshはJavaScriptエンジンを内蔵しており、JavaScriptのコードを実行できます。MongoDBの操作コマンドは、JavaScriptのメソッド呼び出しとして記述します。

2.4 MongoDB Atlas (クラウド) の紹介

自分でサーバーを管理したくない場合や、手軽に試したい場合は、MongoDB Atlasという公式のクラウドサービスを利用するのが便利です。主要なクラウドプロバイダー(AWS, Google Cloud, Azure)上でマネージドなMongoDBクラスターを提供しています。

Atlasには無料枠(M0クラスター)があり、学習目的であれば無料で利用できます。

Atlasの利用手順(概要):

  1. MongoDB Cloudのウェブサイト(https://www.mongodb.com/cloud/atlas)にアクセスし、アカウントを作成します。
  2. 新しいプロジェクトを作成します。
  3. 新しいクラスターを作成します。無料枠のM0クラスターを選択し、プロバイダーやリージョンを指定します。
  4. セキュリティ設定を行います。データベースユーザーを作成し、接続元IPアドレス(自宅のIPアドレスやどこからでも接続許可)を設定します。
  5. 作成したクラスターへの接続方法を取得します。SRVレコード接続文字列や標準接続文字列などを利用できます。Compassやmongosh、アプリケーションからこの接続情報を使ってAtlasのクラスターに接続します。

Atlasを使えば、インストールやサーバー管理の手間なく、すぐにMongoDBを使い始めることができます。本記事ではローカル環境での操作を例に進めますが、Atlasを使っている場合も、基本的な操作コマンドは同じです。

3. MongoDBの基本概念:データベース、コレクション、ドキュメント

MongoDBの世界におけるデータの入れ物について、もう少し詳しく見ていきましょう。

  • データベース (Database): 複数のコレクションをまとめる論理的なコンテナです。RDBにおけるデータベースと同じ概念と考えて差し支えありません。例えば、Webアプリケーションごとに1つのデータベースを作成するといった使い方をします。
  • コレクション (Collection): ドキュメントの集合です。RDBにおけるテーブルに相当しますが、コレクション内のドキュメントは厳格なスキーマを共有する必要はありません。例えば、ユーザー情報を格納するusersコレクション、商品情報を格納するproductsコレクションのように、関連性の高いドキュメントをまとめて格納します。
  • ドキュメント (Document): MongoDBにおけるデータの基本単位です。RDBにおける行(レコード)に相当しますが、ドキュメントはJSONまたはBSON形式で表現され、ネストされたデータ構造(ドキュメント内にドキュメントや配列を含む)を持つことができます。各ドキュメントは、コレクション内で一意な_idフィールドを持ちます。

3.1 BSON形式とデータ型

ドキュメントは主にBSON形式で表現されます。BSONはJSONの拡張であり、JSONで表現できるデータ構造に加え、以下のようないくつかの追加のデータ型をサポートしています。

  • ObjectId: ドキュメントの一意なIDとしてよく使われます。12バイトのユニークな識別子です。
  • Date: 日付と時刻を格納します。
  • Binary data: バイナリデータを格納します。
  • Regular Expression: 正規表現を格納します。
  • Undefined: Undefined値を格納します。
  • Null: Null値を格納します。
  • Integer (32-bit and 64-bit): 整数値を格納します。
  • Double: 浮動小数点数を格納します。
  • Decimal128: 10進数を正確に表現する必要がある場合(例: 金融データ)に使用します。

BSONはJSONよりも効率的にパースでき、より多くのデータ型をサポートするため、内部的なデータ形式として利用されます。mongoshやCompassでは、BSONデータは通常JSONライクな形式で表示されるため、JSONとして扱っている感覚で操作できます。

3.2 _id フィールドの重要性

各ドキュメントには、必ず_idという名前の特別なフィールドがあります。このフィールドは、コレクション内で各ドキュメントを一意に識別するためのプライマリキーのような役割を果たします。

  • ドキュメントを挿入する際に_idフィールドを指定しなかった場合、MongoDBドライバーまたはmongodプロセスが自動的にObjectId型の値を生成して付与します。
  • _idフィールドの値は、コレクション内で重複してはなりません。
  • _idフィールドには、ObjectId型だけでなく、整数、文字列、日付など、様々なデータ型の値を使用できます。ただし、一意性を保証する必要があります。
  • _idフィールドには自動的にインデックスが作成されます。

例えば、以下は簡単なドキュメントの例です。

json
{
"_id": ObjectId("60a7c7b9a1d3b4c5e6f7g8h9"), // 自動生成された_id
"name": "山田太郎",
"age": 30,
"city": "東京",
"interests": ["読書", "旅行", "プログラミング"]
}

name, age, city, interestsがフィールドです。interestsは文字列の配列になっています。

4. CRUD操作:MongoDBでデータを扱う基本(作成・読み取り・更新・削除)

データベースの操作の基本はCRUD、すなわちCreate (作成)、Read (読み取り)、Update (更新)、Delete (削除) です。MongoDBにおけるこれらの操作方法を、mongoshを使って詳しく見ていきましょう。

まずは、作業用のデータベースとコレクションを準備しましょう。mongoshを起動し、任意のデータベースに切り替えます。

javascript
myappdb> use myappdb; // myappdbデータベースに切り替え (なければ作成)
myappdb>

特にコレクションを作成するコマンドはありません。データを初めて挿入する際に、指定した名前のコレクションが自動的に作成されます。ここでは例としてusersというコレクションを使います。

4.1 C – Create (作成)

新しいドキュメントをコレクションに挿入します。

insertOne(): 単一のドキュメントを挿入

javascript
myappdb> db.users.insertOne({
name: "山田太郎",
age: 30,
city: "東京"
});

実行すると、挿入されたドキュメントの_idを含む結果が返されます。

json
{
acknowledged: true,
insertedId: ObjectId("...") // 自動生成されたID
}

_idを指定して挿入することも可能です。

javascript
myappdb> db.users.insertOne({
_id: "user123", // 文字列のIDを指定
name: "佐藤花子",
age: 25,
city: "大阪"
});

_idフィールドは必須ではありませんが、自動生成されたObjectIdは通常、ユニークで分散環境でも扱いやすい形式です。

insertMany(): 複数のドキュメントを一度に挿入

複数のドキュメントを効率的に挿入するにはinsertMany()を使います。引数にはドキュメントの配列を渡します。

javascript
myappdb> db.users.insertMany([
{ name: "田中一郎", age: 40, city: "福岡" },
{ name: "鈴木恵子", age: 35, city: "札幌", interests: ["料理", "映画"] },
{ name: "高橋健太", age: 22, city: "東京", status: "student" } // 異なるフィールドを持つドキュメントもOK
]);

json
{
acknowledged: true,
insertedIds: {
'0': ObjectId("..."),
'1': ObjectId("..."),
'2': ObjectId("...")
}
}

このように、同じコレクション内でもドキュメントによって持つフィールドが異なっていても問題ありません。これがスキーマフレキシブルということです。

4.2 R – Read (読み取り)

コレクションからドキュメントを取得します。最もよく使う操作です。

find(): ドキュメントを検索

find()メソッドは、コレクションからドキュメントを検索します。引数にクエリ条件を指定できます。

  • 全件取得: 引数を指定しない場合、コレクション内の全てのドキュメントを取得します。

    javascript
    myappdb> db.users.find();

    結果はカーソルとして返されますが、mongoshはデフォルトで最初の20件を表示します。「Type “it” for more」と表示されたら、itと入力することで次の20件が表示されます。

  • 条件を指定して取得 (クエリセレクター): 最初の引数に、検索条件を表すドキュメントを指定します。

    javascript
    myappdb> db.users.find({ city: "東京" }); // cityが「東京」のドキュメントを取得

    複数の条件を指定すると、それらの条件がANDで結合されます。

    javascript
    myappdb> db.users.find({ city: "東京", age: 30 }); // cityが「東京」かつageが30のドキュメントを取得

    クエリセレクターの例:
    検索条件では、比較演算子や論理演算子など、様々なセレクターを使用できます。セレクターは$記号で始まります。

    • 比較演算子:

      • $eq: 等しい (フィールド名: 値 の形式は $eq の省略形)
      • $ne: 等しくない
      • $gt: より大きい
      • $gte: より大きいか等しい
      • $lt: より小さい
      • $lte: より小さいか等しい
      • $in: 配列内のいずれかの値に等しい
      • $nin: 配列内のいずれの値にも等しくない

      javascript
      myappdb> db.users.find({ age: { $gt: 30 } }); // ageが30より大きいドキュメント
      myappdb> db.users.find({ age: { $lte: 25 } }); // ageが25以下のドキュメント
      myappdb> db.users.find({ city: { $in: ["東京", "大阪"] } }); // cityが東京または大阪のドキュメント
      myappdb> db.users.find({ name: { $ne: "山田太郎" } }); // nameが山田太郎ではないドキュメント

    • 論理演算子:

      • $and: 複数の条件が全て真
      • $or: 複数の条件のいずれかが真
      • $not: 条件が偽
      • $nor: 複数の条件のいずれも真でない (全て偽)

      javascript
      myappdb> db.users.find({ $or: [{ city: "東京" }, { city: "大阪" }] }); // cityが東京または大阪
      myappdb> db.users.find({ $and: [{ age: { $gte: 20 } }, { age: { $lt: 30 } }] }); // ageが20以上かつ30未満

      $andは、複数の条件をドキュメントのトップレベルで指定する場合と同じ意味になります。上の例で言えば、{ age: { $gte: 20 }, age: { $lt: 30 } } と書くと、同じフィールドに対する複数の条件は通常最後に書かれたものが有効になるため意図した動作になりません。このような場合に$andを使います。

    • 要素演算子:

      • $exists: フィールドが存在するかどうか
      • $type: フィールドのBSONタイプを指定

      javascript
      myappdb> db.users.find({ interests: { $exists: true } }); // interestsフィールドが存在するドキュメント
      myappdb> db.users.find({ age: { $type: "number" } }); // ageフィールドが数値型のドキュメント

    • 配列演算子:

      • $all: 配列フィールドが、指定した配列の要素を全て含む
      • $elemMatch: 配列フィールド内の要素が、指定した複数の条件を全て満たすドキュメント
      • $size: 配列の要素数が指定した値と等しい

      “`javascript
      // interestsが「料理」と「映画」の両方を含むドキュメント
      myappdb> db.users.find({ interests: { $all: [“料理”, “映画”] } });

      // comments配列があり、その中にauthorが”Alice”かつscoreが5より大きい要素があるドキュメント
      // (commentsが [ { author: “Alice”, score: 6 }, { author: “Bob”, score: 3 } ] のような場合)
      myappdb> db.posts.find({ comments: { $elemMatch: { author: “Alice”, score: { $gt: 5 } } } });

      // interests配列の要素数が2個のドキュメント
      myappdb> db.users.find({ interests: { $size: 2 } });
      “`

  • 正規表現を使った検索: $regexセレクターを使って正規表現で文字列を検索できます。

    javascript
    myappdb> db.users.find({ name: { $regex: /^山田/ } }); // nameが「山田」で始まるドキュメント
    myappdb> db.users.find({ city: { $regex: /京$/ } }); // cityが「京」で終わるドキュメント
    myappdb> db.users.find({ name: { $regex: /田中/, $options: "i" } }); // nameに「田中」を含む(大文字小文字を区別しない)

findOne(): 単一のドキュメントを取得

条件に一致するドキュメントのうち、最初の1件だけを取得したい場合はfindOne()を使います。find()と同様に引数にクエリ条件を指定できます。

javascript
myappdb> db.users.findOne({ _id: "user123" }); // _idが"user123"のドキュメントを取得
myappdb> db.users.findOne({ city: "福岡" }); // cityが「福岡」の最初のドキュメントを取得

一致するドキュメントがない場合はnullが返されます。

プロジェクション(Projection):取得するフィールドを選択

find()メソッドの第2引数に、取得したいフィールドを指定するドキュメントを渡すことで、結果に含めるフィールドを選択できます。これをプロジェクションと呼びます。不要なフィールドを除外することで、ネットワークトラフィックやメモリ使用量を削減できます。

フィールド名に1を指定するとそのフィールドを含め、0を指定すると除外します。_idフィールドはデフォルトで含まれますが、明示的に_id: 0と指定すれば除外できます。

javascript
myappdb> db.users.find({}, { name: 1, city: 1 }); // 全てのドキュメントから、nameとcityフィールドのみを取得 (_idもデフォルトで含まれる)
myappdb> db.users.find({ city: "東京" }, { name: 1, age: 1, _id: 0 }); // cityが東京のドキュメントから、nameとageフィールドのみを取得 (_idは除外)

プロジェクションで含むフィールドと除外するフィールドを同時に指定することはできません(ただし_idフィールドは例外として、他のフィールドを含めつつ_id: 0で除外することが可能です)。

sort(): 結果のソート

find()メソッドの結果(カーソル)に対して、sort()メソッドを使ってソート順を指定できます。sort()の引数には、ソートキーとなるフィールドとソート順(昇順なら1、降順なら-1)を指定するドキュメントを渡します。

javascript
myappdb> db.users.find().sort({ age: 1 }); // ageフィールドの昇順でソート
myappdb> db.users.find({ city: "東京" }).sort({ name: -1 }); // cityが東京のドキュメントをnameフィールドの降順でソート
myappdb> db.users.find().sort({ city: 1, age: -1 }); // cityの昇順でソートし、cityが同じ場合はageの降順でソート

limit(): 取得件数の制限

find()メソッドの結果に対して、limit()メソッドを使って取得するドキュメントの最大件数を制限できます。

javascript
myappdb> db.users.find().limit(3); // 最初の3件を取得

skip(): 結果のスキップ

find()メソッドの結果に対して、skip()メソッドを使って先頭から指定した数のドキュメントをスキップできます。ページネーションなどに利用できますが、件数が多くなるとパフォーマンスの問題が発生する可能性があるため注意が必要です。

javascript
myappdb> db.users.find().skip(2).limit(3); // 最初の2件をスキップし、その後の3件を取得

sort(), limit(), skip()はメソッドチェーンとして組み合わせて使用するのが一般的です。メソッドを呼び出す順番によって結果が変わることは基本的にありません(内部で最適な順番に処理されるため)が、通常は find() -> sort() -> skip() -> limit() の順で記述します。

javascript
myappdb> db.users.find({ city: "東京" }, { name: 1, age: 1, _id: 0 })
.sort({ age: -1 })
.skip(1)
.limit(2);
// cityが東京のドキュメントからnameとageを取得し、ageの降順でソートした結果の2件目から2件を取得

4.3 U – Update (更新)

既存のドキュメントの内容を変更します。

updateOne(): 条件に一致する最初のドキュメントを更新

クエリ条件に一致するドキュメントのうち、最初の1件だけを更新します。第1引数に更新対象を特定するクエリ条件、第2引数に更新内容を指定します。

javascript
myappdb> db.users.updateOne(
{ name: "山田太郎" }, // 更新対象のクエリ
{ $set: { city: "神奈川" } } // 更新内容: cityフィールドを「神奈川」に設定
);

更新内容には、$setなどの更新演算子を使用します。

updateMany(): 条件に一致する全てのドキュメントを更新

クエリ条件に一致する全てのドキュメントを更新します。

javascript
myappdb> db.users.updateMany(
{ age: { $gte: 30 } }, // 更新対象のクエリ: ageが30以上のドキュメント
{ $set: { status: "adult" } } // 更新内容: statusフィールドを「adult」に設定
);

更新演算子(Update Operators)

更新内容を指定する際には、フィールドの値を直接指定するのではなく、更新演算子を使用するのが一般的です。これにより、フィールドの追加、削除、数値の増減、配列要素の操作など、様々な更新操作を行えます。よく使われる更新演算子をいくつか紹介します。

  • $set: フィールドの値、またはドキュメントや配列内の要素の値を設定します。存在しないフィールドを指定した場合、そのフィールドが新しく追加されます。

    javascript
    myappdb> db.users.updateOne({ name: "鈴木恵子" }, { $set: { city: "仙台", occupation: "Engineer" } });
    // 鈴木恵子のcityを仙台に更新し、occupationフィールドを追加

  • $unset: フィールドをドキュメントから削除します。

    javascript
    myappdb> db.users.updateMany({ status: "student" }, { $unset: { status: "" } }); // statusがstudentのドキュメントからstatusフィールドを削除

    $unsetの値は何を指定しても構いません(通常は空文字列やnull)。

  • $inc: 数値フィールドの値を指定した量だけ増減させます。存在しない数値フィールドを指定した場合、そのフィールドが初期値0として追加され、指定した量だけ増減されます。

    javascript
    myappdb> db.users.updateOne({ name: "山田太郎" }, { $inc: { age: 1 } }); // 山田太郎のageを1増やす

  • $push: 配列フィールドに新しい要素を追加します。フィールドが存在しない場合は、新しい配列として作成されます。

    javascript
    myappdb> db.users.updateOne({ name: "山田太郎" }, { $push: { interests: "写真" } }); // 山田太郎のinterests配列に「写真」を追加

  • $pop: 配列の先頭または末尾から要素を削除します。1を指定すると末尾、-1を指定すると先頭の要素が削除されます。

    javascript
    myappdb> db.users.updateOne({ name: "山田太郎" }, { $pop: { interests: 1 } }); // 山田太郎のinterests配列の末尾要素を削除

  • $pull: 配列フィールドから、指定した条件に一致する全ての要素を削除します。

    javascript
    myappdb> db.users.updateMany({}, { $pull: { interests: "読書" } }); // 全てのドキュメントのinterests配列から「読書」という要素を全て削除

Upsert(更新または挿入)

updateOne()updateMany()の第3引数に{ upsert: true }オプションを指定すると、クエリ条件に一致するドキュメントが存在しない場合に、新しいドキュメントを挿入できます。挿入されるドキュメントは、クエリ条件と更新内容($setなどで指定された部分)を組み合わせたものになります。

javascript
myappdb> db.users.updateOne(
{ name: "木村次郎" }, // この名前のユーザーは存在しないとする
{ $set: { age: 28, city: "名古屋" } },
{ upsert: true } // upsertオプションを有効にする
);
// nameが「木村次郎」のドキュメントは存在しないので、新しいドキュメント { name: "木村次郎", age: 28, city: "名古屋", _id: ObjectId(...) } が挿入される

4.4 D – Delete (削除)

コレクションからドキュメントを削除します。

deleteOne(): 条件に一致する最初のドキュメントを削除

クエリ条件に一致するドキュメントのうち、最初の1件だけを削除します。

javascript
myappdb> db.users.deleteOne({ name: "山田太郎" }); // nameが「山田太郎」の最初のドキュメントを削除

deleteMany(): 条件に一致する全てのドキュメントを削除

クエリ条件に一致する全てのドキュメントを削除します。引数に空のドキュメント{}を指定すると、コレクション内の全てのドキュメントを削除できます(コレクション自体は残ります)。

javascript
myappdb> db.users.deleteMany({ age: { $lt: 25 } }); // ageが25未満の全てのドキュメントを削除
myappdb> db.users.deleteMany({}); // コレクション内の全てのドキュメントを削除

drop(): コレクション自体を削除

コレクション内の全てのドキュメントを削除するだけでなく、コレクション自体を完全に削除するにはdrop()メソッドを使います。

javascript
myappdb> db.users.drop(); // usersコレクションを削除

この操作は元に戻せないので注意が必要です。

ここまでで、MongoDBにおける基本的なCRUD操作の方法を学びました。これらの操作を組み合わせることで、データの追加、検索、変更、削除といった基本的なデータベース操作を自由に行えるようになります。

5. 高度なトピック(入門レベルで押さえたいもの)

基本的なCRUD操作に加え、MongoDBをより効果的に使うために知っておくべき、入門レベルでの高度なトピックをいくつか紹介します。

5.1 インデックス (Indexes)

インデックスは、クエリの実行速度を劇的に向上させるための重要な仕組みです。RDBと同様に、MongoDBもインデックスをサポートしています。インデックスを作成すると、MongoDBは指定されたフィールドの値に基づいた効率的な検索可能なデータ構造(通常はB-tree)を作成します。クエリを実行する際に、コレクション全体をスキャンする代わりにこのインデックスを使用することで、検索対象のドキュメントを素早く見つけ出すことができます。

なぜインデックスが必要か?

コレクション内のドキュメント数が増えるにつれて、インデックスがない場合のクエリは遅くなります。特に、find()sort()操作は、適切なインデックスがないとパフォーマンスが低下しやすいです。頻繁にクエリ条件やソートキーとして使用するフィールドにはインデックスを作成することを検討すべきです。

インデックスの種類

MongoDBはいくつかの種類のインデックスをサポートしています。

  • 単一フィールドインデックス: 1つのフィールドに対して作成する基本的なインデックスです。昇順 (1) または降順 (-1) で作成できます。
  • 複合インデックス (Compound Indexes): 複数のフィールドを組み合わせたインデックスです。クエリ条件やソート条件が複数のフィールドに関わる場合に有効です。インデックスのフィールドの順序が重要になります。
  • マルチキーインデックス (Multikey Indexes): ドキュメント内の配列フィールドに対して作成されます。配列の要素ごとにインデックスエントリが作成されます。
  • テキストインデックス (Text Indexes): 文字列フィールドのテキスト検索(全文検索)をサポートするためのインデックスです。
  • 地理空間インデックス (Geospatial Indexes): 地理空間データ(座標情報など)に対するクエリ(例: 特定の場所の近くのドキュメントを検索)をサポートするためのインデックスです。

インデックスの作成

createIndex()メソッドを使ってインデックスを作成します。第1引数にインデックスを作成するフィールドとソート順(単一フィールドまたは複合の場合)、第2引数にオプション(インデックスの種類など)を指定します。

javascript
myappdb> db.users.createIndex({ city: 1 }); // cityフィールドに対する昇順の単一フィールドインデックスを作成

複合インデックスの例:

javascript
myappdb> db.users.createIndex({ city: 1, age: -1 }); // cityの昇順、ageの降順で複合インデックスを作成

この複合インデックスは、{ city: "東京", age: { $gt: 30 } } のようなクエリや、find({ city: "東京" }).sort({ age: -1 }) のようなソートを含むクエリで効果を発揮しやすいです。

テキストインデックスの例:

javascript
myappdb> db.products.createIndex({ description: "text" }); // descriptionフィールドにテキストインデックスを作成
// テキスト検索の例: db.products.find({ $text: { $search: "quick brown fox" } })

インデックスの確認

getIndexes()メソッドを使って、コレクションに作成されているインデックスを確認できます。

javascript
myappdb> db.users.getIndexes();

json
[
{ // _idに自動作成されるインデックス
v: 2,
key: { _id: 1 },
name: '_id_'
},
{ // 先ほど作成したcityフィールドのインデックス
v: 2,
key: { city: 1 },
name: 'city_1'
},
{ // 先ほど作成した複合インデックス
v: 2,
key: { city: 1, age: -1 },
name: 'city_1_age_-1'
}
]

インデックスの削除

dropIndex()またはdropIndexes()メソッドを使ってインデックスを削除できます。インデックス名を指定して削除するのが一般的です。インデックス名はgetIndexes()で確認できます。

javascript
myappdb> db.users.dropIndex("city_1"); // city_1という名前のインデックスを削除
myappdb> db.users.dropIndexes(); // _idインデックス以外の全てのインデックスを削除

インデックスはクエリを高速化しますが、データの挿入、更新、削除のパフォーマンスにはオーバーヘッドが発生します(インデックスも更新する必要があるため)。そのため、やみくもに多くのインデックスを作成するのではなく、アプリケーションのクエリパターンを分析し、効果的なインデックスを設計することが重要です。

5.2 アグリゲーションパイプライン (Aggregation Pipeline)

アグリゲーションパイプラインは、MongoDBで複雑なデータ集計や変換を行うための強力なフレームワークです。RDBにおけるGROUP BYを使った集計、結合、データの整形など、複数のステップを経て結果を得るような処理をMongoDBで行う際に使用します。

アグリゲーションパイプラインは、ドキュメントの流れ(パイプライン)をイメージすると分かりやすいです。入力となるドキュメント群が、順番に複数の「ステージ (Stage)」を通過する間に、フィルタリング、変換、グループ化、ソートなどの処理が行われ、最終的な結果が出力されます。

アグリゲーションパイプラインはaggregate()メソッドを使って実行します。引数には、実行したいステージの配列を渡します。

javascript
myappdb> db.users.aggregate([
// Stage 1: ageが30以上のドキュメントのみをフィルタリング
{ $match: { age: { $gte: 30 } } },
// Stage 2: city別にグループ化し、各グループのユーザー数をカウント
{ $group: { _id: "$city", count: { $sum: 1 } } },
// Stage 3: カウントが多い順にソート
{ $sort: { count: -1 } },
// Stage 4: 上位3件のみを取得
{ $limit: 3 }
]);

この例では、以下の4つのステージを順に実行しています。

  1. $match: 入力ドキュメントをフィルタリングします。find()のクエリ条件と同じシンタックスを使用できます。これにより、パイプラインの後続ステージに渡されるドキュメントの数を減らし、パフォーマンスを向上させることができます。
  2. $group: 指定したキー (_id) でドキュメントをグループ化し、集計関数 ($sum, $avg, $max, $minなど) を使って各グループの集計値を計算します。この例では_id: "$city"でcityフィールドの値ごとにグループ化し、$sum: 1で各グループのドキュメント数をカウントしています(各ドキュメントに対して1を加算することで数を数える)。
  3. $sort: ドキュメントを指定したキーでソートします。sort()メソッドと同様のシンタックスです。
  4. $limit: 結果として出力するドキュメントの最大数を制限します。limit()メソッドと同様のシンタックスです。

その他のよく使うステージ:

  • $project: 入力ドキュメントから出力ドキュメントの形式を整形します。含めるフィールド、除外するフィールド、新しいフィールドの追加、既存フィールドの計算や変換などを行えます。RDBのSELECT句に相当します。
    javascript
    // nameとageフィールドのみを含み、_idを除外
    { $project: { name: 1, age: 1, _id: 0 } }
    // nameと age*2 という計算結果のdouble_ageフィールドを含める
    { $project: { name: 1, double_age: { $multiply: ["$age", 2] }, _id: 0 } }
  • $unwind: 配列フィールドを持つドキュメントを、配列の各要素ごとに分割します。例えば、interests: ["読書", "旅行"]というドキュメントがあった場合、このステージを適用すると { ..., interests: "読書" }{ ..., interests: "旅行" } の2つのドキュメントとして扱われます。これにより、配列要素ごとの集計などが容易になります。

アグリゲーションパイプラインは非常に強力で、複雑なデータ分析やレポート作成に不可欠です。最初は難しく感じるかもしれませんが、各ステージの役割を理解し、小さなパイプラインから試していくのが良いでしょう。

6. データのモデリング:組み込みと参照

MongoDBにおけるデータモデリングは、RDBとは異なるアプローチが必要です。リレーショナルモデルのような厳格な正規化は必須ではなく、アプリケーションのアクセスパターンやデータの関連性に応じて、データを1つのドキュメントにまとめる「組み込み(Embedded)」と、別のドキュメントを参照する「参照(Reference)」を使い分けることが重要になります。

6.1 リレーショナルモデル vs ドキュメントモデル

  • リレーショナルモデル: データをテーブルに分割し、共通の列を使って関連付けます(正規化)。データの重複を避けることが主な目的です。データの取得にはJOIN操作が必要になります。
  • ドキュメントモデル: 関連性の高いデータを1つのドキュメント内に格納したり、別のドキュメントへの参照を格納したりします。データの重複を許容することで、JOINなしでデータを取得できる利点があります(非正規化)。

6.2 組み込み (Embedded)

あるドキュメントを別のドキュメントのフィールドにネスト(埋め込み)する方法です。これは、関連するデータが論理的に一体であり、かつ以下の条件を満たす場合に適しています。

  • 1対1 または 1対「少数の」多 の関係: 例えば、ユーザーとそのプロフィール情報(住所、電話番号など)は1対1の関係なので、ユーザードキュメント内にプロフィール情報を組み込むのが自然です。ブログ記事とコメントのように1対多の関係でも、コメント数が「少数」であれば記事ドキュメント内にコメント配列を組み込むことができます。
  • 一緒に取得・更新されることが多い: 親ドキュメントを取得する際に、子ドキュメント(組み込みデータ)も一緒に取得したい場合に適しています。
  • 組み込みデータのサイズや数が上限を超えない: MongoDBのドキュメントサイズには最大16MBという制限があります。また、配列要素が非常に多くなるとパフォーマンスに影響が出る可能性があります。

組み込みの例:

ブログ記事とそのコメント(コメント数が少ない場合を想定)

json
{
"_id": ObjectId(...),
"title": "MongoDB入門",
"content": "...",
"author": "Alice",
"tags": ["MongoDB", "NoSQL", "Database"],
"comments": [
{
"author": "Bob",
"text": "とても分かりやすいです!",
"date": ISODate(...)
},
{
"author": "Charlie",
"text": "続きが楽しみです!",
"date": ISODate(...)
}
]
}

このモデルの利点:

  • 記事を取得するだけでコメントも一緒に取得できるため、読み込みが高速。
  • JOIN操作が不要。

このモデルの欠点:

  • コメント数が非常に多くなるとドキュメントサイズが大きくなりすぎる可能性がある。
  • コメント単体を更新・削除する際に、親ドキュメント全体を読み込んで変更し、書き戻す必要がある($push, $pull, $elemMatchなどの演算子を使えば効率化できる場合もある)。
  • 特定のコメントを投稿者別に検索する場合、記事ドキュメント全体をスキャンする必要がある。

6.3 参照 (Reference)

あるドキュメントから別のドキュメントの_idをフィールドに格納し、関連を示す方法です。RDBにおける外部キー(Foreign Key)に似ています。これは、関連するデータが以下の条件を満たす場合に適しています。

  • 1対「多数の」多 または 多対多 の関係: 例えば、ユーザーとその注文(ユーザーは多数の注文を持つ)や、書籍とその著者(書籍は複数の著者を持つ、著者も複数の書籍に関わる)のような関係。
  • 単体で独立して存在・取得されることが多い: 組み込みデータとしてではなく、それ自体が独立したエンティティとして頻繁にアクセスされる場合。
  • 組み込みデータが巨大化する可能性がある: 前述のドキュメントサイズ制限やパフォーマンス懸念がある場合。

参照の例:

ユーザーと注文(ユーザーは多数の注文を持つ)

usersコレクション:
json
{
"_id": ObjectId("user_abc"),
"name": "山田太郎",
"email": "[email protected]"
}

ordersコレクション:
json
{
"_id": ObjectId("order_xyz"),
"user_id": ObjectId("user_abc"), // ユーザーコレクションの_idを参照
"order_date": ISODate(...),
"items": [ ... ],
"total_amount": 5000
}

このモデルの利点:

  • ユーザーの全注文を取得する場合など、注文数が多くてもドキュメントサイズの問題が発生しない。
  • 注文ドキュメントは独立して操作できる。
  • ユーザー情報と注文情報が独立して更新される場合でも影響が少ない。

このモデルの欠点:

  • ユーザー情報と関連する注文情報を同時に取得するには、2回以上のクエリが必要になる(まずユーザーを取得し、その_idを使って注文コレクションをクエリする)。これはJOIN操作に相当しますが、MongoDBではアプリケーション側で複数のクエリを実行してデータを組み合わせるのが一般的です(「アプリケーションサイドJOIN」)。アグリゲーションパイプラインの$lookupステージを使えば、サーバーサイドでJOINライクな処理を行うことも可能です。

6.4 どちらを選択すべきか?

組み込みと参照のどちらを使うかは、アプリケーションの具体的なユースケースとアクセスパターンに依存します。

  • データの読み込みパフォーマンスを最優先し、関連データが常に一緒に必要で、かつサイズが制限内に収まる場合は、組み込みを検討しましょう。
  • データの整合性、独立した操作、データの増加に対する柔軟性を重視する場合、または関連データが大きくなりすぎる場合は、参照を検討しましょう。

MongoDBはスキーマが柔軟であるため、最初は組み込みで始めて、データが増えてきたら参照に切り替える、といった段階的なモデリングも可能です。しかし、大規模なデータの移行は大変な作業になるため、初期段階でしっかりとアクセスパターンを考慮した設計を行うことが望ましいです。

7. MongoDBの運用と管理(簡単な紹介)

入門レベルでは深い理解は不要ですが、MongoDBがどのように信頼性やスケーラビリティを確保しているのか、基本的な仕組みを知っておくことは役立ちます。

7.1 レプリカセット (Replica Set)

レプリカセットは、複数のMongoDBサーバー(ノード)で構成される冗長化されたクラスターです。データを複数のノードに複製することで、単一障害点(SPOF)を排除し、高可用性(High Availability)を実現します。

レプリカセットには、1つのプライマリノードと、1つ以上のセカンダリノードがあります。

  • プライマリノード: 全ての書き込み処理を受け付けます。
  • セカンダリノード: プライマリノードからデータを非同期または同期的に複製します。読み込み処理を受け付けるように設定することも可能です。

プライマリノードに障害が発生した場合、レプリカセットのメンバーは自動的にプライマリノードを選出し直し(フェイルオーバー)、サービスの停止時間を最小限に抑えます。

7.2 シャーディング (Sharding)

シャーディングは、大量のデータを複数のMongoDBサーバー(シャード)に分散させる手法です。これにより、データ量や読み書き負荷の増加に対して水平スケーラビリティ(サーバー台数を増やすことで性能を向上させる)を実現します。

シャーディングクラスターは以下のコンポーネントで構成されます。

  • シャード (Shards): データを実際に格納するMongoDBレプリカセットです。
  • Config Server: クラスター全体のメタデータ(どのシャードにどのデータが格納されているかなど)を格納します。レプリカセットとして構成されます。
  • mongos (Query Router): クライアントからのクエリを受け付け、Config Serverを参照して適切なシャードにルーティングします。複数のmongosプロセスを立てることで冗長化します。

データをどのシャードに分散するかは、シャーディングキーと呼ばれるフィールドの値に基づいて決定されます。シャーディングキーの選択は、シャーディングクラスターのパフォーマンスにとって非常に重要です。

レプリカセットとシャーディングは、MongoDBがエンタープライズレベルの要件に対応できる基盤技術です。ローカル環境での学習段階では意識することは少ないかもしれませんが、本番環境でMongoDBを利用する際にはこれらの設計と構築が重要になります。

7.3 セキュリティ

データベースのセキュリティは非常に重要です。MongoDBでは、以下のようないくつかのセキュリティ対策が可能です。

  • 認証: データベースにアクセスするユーザーを検証します。パスワード認証やX.509証明書認証などがあります。
  • 認可: 認証されたユーザーに対して、どのデータベースやコレクションに対してどのような操作(読み込み、書き込みなど)を許可するかを制御します。ロールベースのアクセス制御 (RBAC) が推奨されます。
  • ネットワークセキュリティ: ファイアウォールを使って信頼できるIPアドレスからのアクセスのみを許可したり、TLS/SSLを使って通信を暗号化したりします。
  • 保存データの暗号化: ストレージレベルでのデータの暗号化を設定できます。

7.4 バックアップとリストア

データの損失はビジネスに大きな影響を与えるため、定期的なバックアップは必須です。MongoDBは、mongodump(データをBSON形式でエクスポート)やスナップショット(ストレージレベルでのバックアップ)などの方法でバックアップをサポートしています。リストアにはmongorestoreツールを使用します。MongoDB Atlasを利用している場合は、自動バックアップ機能が提供されています。

8. まとめと次のステップ

本記事では、MongoDBをゼロから始める方のために、その基本的な概念、インストール方法、CRUD操作、インデックス、アグリゲーション、データモデリングといった主要なトピックを詳細に解説しました。

この記事で学んだことの要約:

  • MongoDBはドキュメント指向のNoSQLデータベースであり、柔軟なスキーマとスケーラビリティが特徴である。
  • RDBとの概念(データベース、テーブル、行、列)とMongoDBの概念(データベース、コレクション、ドキュメント、フィールド)の対応関係。
  • BSON形式と_idフィールドの重要性。
  • mongoshやMongoDB Compassを使った基本的な操作方法。
  • insertOne(), insertMany()でのドキュメント作成。
  • find(), findOne()でのドキュメント読み取り(クエリセレクター、プロジェクション、ソート、リミット、スキップを含む)。
  • updateOne(), updateMany()でのドキュメント更新($set, $inc, $pushなどの更新演算子、Upsertオプションを含む)。
  • deleteOne(), deleteMany(), drop()でのドキュメント・コレクション削除。
  • クエリパフォーマンスを向上させるためのインデックスの役割と作成方法。
  • 複雑な集計や変換を行うためのアグリゲーションパイプラインの基本的なステージ。
  • データモデリングにおける組み込みと参照の考え方と使い分け。
  • MongoDBの運用を支えるレプリカセット、シャーディングといった技術の簡単な概要。

MongoDBは非常に多機能で奥深いデータベースです。本記事はあくまで「ゼロから始める」ための基礎の基礎に過ぎません。ここからさらにMongoDBを使いこなすためには、実践と継続的な学習が不可欠です。

次のステップ:

  1. 実践: 実際に手を動かして、記事で学んだCRUD操作やインデックス、アグリゲーションを試してみてください。自分の小さなプロジェクトでMongoDBを使ってみるのが一番の学習になります。
  2. 公式ドキュメント: MongoDBの公式ドキュメントは非常に詳細で充実しています。より深い情報を知りたい場合や、特定の機能について詳しく学びたい場合は、公式ドキュメント(https://docs.mongodb.com/)を参照してください。特に、クエリセレクター、更新演算子、アグリゲーションステージのリファレンスは役立ちます。
  3. 各種ドライバー: 実際のアプリケーション開発では、お使いのプログラミング言語のMongoDBドライバーを使うことになります。Node.js (Mongoose/Node-MongoDB-Driver), Python (PyMongo), Java, C#など、様々な言語向けのドライバーのドキュメントを確認し、アプリケーションからMongoDBを操作する方法を学んでください。
  4. MongoDB Atlas: クラウドサービスであるMongoDB Atlasを使ってみるのも良い経験になります。無料枠で基本的な機能や簡単なクラスター構成を試せます。
  5. コミュニティ: MongoDBのフォーラムやStack Overflowなどで質問したり、他のユーザーの質問を見たりすることで、多くの学びが得られます。

MongoDBは、その柔軟性とスケーラビリティから、現代の多様なアプリケーション要件に適した強力なデータベースです。この記事が、あなたがMongoDBの世界に飛び込むための確かな一歩となることを願っています。


コメントする

メールアドレスが公開されることはありません。 が付いている欄は必須項目です

上部へスクロール