著者は、 Open Internet / Free Speech Fund を選択して、 Write forDOnationsプログラムの一環として寄付を受け取りました。
序章
MongoDBはドキュメント指向のデータベース管理システムであり、サイズや構造が異なる可能性のある大量のデータをドキュメントに保存できます。 MongoDBは、特定の基準に基づいてドキュメントをフィルタリングできる強力なクエリメカニズムを備えています。 ただし、MongoDBコレクションが増えるにつれて、ドキュメントの検索は干し草の山の中の針の検索のようになる可能性があります。
クエリに関してMongoDBが提供する柔軟性により、データベースエンジンが最も頻繁に使用されるクエリの種類を予測することが困難になる可能性があります。 コレクションのサイズに関係なく、ドキュメントを検索する準備ができている必要があります。 このため、コレクションに保持されるデータの量は検索パフォーマンスに直接影響します。データセットが大きいほど、MongoDBがクエリに一致するドキュメントを見つけるのが難しくなります。
インデックスは、データベース管理者がデータベースエンジンを意識的に支援し、そのパフォーマンスを向上させるために使用できる最も重要なツールの1つです。 このチュートリアルでは、インデックスとは何か、インデックスを作成する方法、およびデータベースがクエリを実行するときにインデックスがどのように使用されるかを確認する方法を学習します。
前提条件
このチュートリアルに従うには、次のものが必要です。
- 通常のroot以外のユーザーがいるサーバー
sudo
特権とUFWで構成されたファイアウォール。 このチュートリアルは、Ubuntu 20.04を実行しているサーバーを使用して検証されており、Ubuntu20.04のこの初期サーバーセットアップチュートリアルに従ってサーバーを準備できます。 - サーバーにインストールされているMongoDB。 これを設定するには、 Ubuntu20.04にMongoDBをインストールする方法に関するチュートリアルに従ってください。
- 認証を有効にして管理ユーザーを作成することにより、サーバーのMongoDBインスタンスを保護します。 このようにMongoDBを保護するには、 Ubuntu20.04でMongoDBを保護する方法に関するチュートリアルに従ってください。
- MongoDB CRUD操作に精通しており、特にコレクションからオブジェクトを取得している。 MongoDBシェルを使用してCRUD操作を実行する方法については、チュートリアルMongoDBでCRUD操作を実行する方法に従ってください。
注:サーバーの構成、インストール、およびMongoDBの安全なインストールの方法に関するリンクされたチュートリアルは、Ubuntu20.04を参照しています。 このチュートリアルは、基盤となるオペレーティングシステムではなく、MongoDB自体に焦点を当てています。 通常、認証が有効になっている限り、オペレーティングシステムに関係なく、すべてのMongoDBインストールで機能します。
インデックスを理解する
通常、MongoDBデータベースにクエリを実行して、特定の条件に一致するドキュメント(8000メートルを超える高さの山頂など)を取得する場合、データベースはそれらを見つけるためにコレクションスキャンを実行する必要があります。 これは、コレクションからすべてのドキュメントを取得して、それらが条件に一致するかどうかを確認することを意味します。 ドキュメントが条件に一致する場合、返されるドキュメントのリストに追加されます。 ドキュメントが指定された条件に一致しない場合、MongoDBは、コレクション全体のスキャンが完了するまで、次のドキュメントのスキャンに進みます。
このメカニズムは多くのユースケースでうまく機能しますが、コレクションが大きくなると著しく遅くなる可能性があります。 コレクションに保存されているドキュメントが複雑な場合、これはより顕著になります。 コレクションのドキュメントが単なる数フィールドではない場合、それらのコンテンツを読み取って分析するのはコストのかかる操作になる可能性があります。
インデックスは、コレクションのドキュメントに保持されているデータのごく一部のみをドキュメント自体とは別に格納する特別なデータ構造です。 MongoDBでは、値を検索するときにデータベースがすばやく効率的にトラバースできるように実装されています。
インデックスを理解しやすくするために、オンラインストアに製品を保存しているデータベースコレクションを想像してみてください。 各製品は、画像、詳細な説明、カテゴリの関係、およびその他の多くのフィールドを含むドキュメントで表されます。 アプリケーションは、このコレクションに対してクエリを頻繁に実行して、在庫のある製品を確認します。
インデックスがない場合、MongoDBはコレクションからすべての製品を取得し、ドキュメント構造の在庫情報を確認する必要があります。 ただし、インデックスを使用すると、MongoDBは、在庫のある製品へのポインターのみを含む、別個のより小さなリストを維持します。 その後、MongoDBはこの構造を使用して、在庫のある製品をより迅速に見つけることができます。
次の手順では、サンプルデータベースを準備し、それを使用してさまざまなタイプのインデックスを作成します。 クエリを実行するときにインデックスが使用されているかどうかを確認する方法を学習します。 最後に、以前に定義したインデックスを一覧表示し、必要に応じてそれらを削除する方法を学習します。
ステップ1—サンプルデータベースの準備
インデックスがどのように機能し、どのように作成するかを学ぶために、このステップでは、MongoDBシェルを開いてローカルにインストールされたMongoDBインスタンスに接続する方法の概要を説明します。 また、サンプルコレクションを作成し、それにいくつかのサンプルドキュメントを挿入する方法についても説明します。 このガイドでは、このサンプルデータを使用して、MongoDBがクエリのパフォーマンスを向上させるために使用できるさまざまなタイプのインデックスについて説明します。
このサンプルコレクションを作成するには、管理ユーザーとしてMongoDBシェルに接続します。 このチュートリアルは、前提条件 MongoDBセキュリティチュートリアルの規則に従い、この管理ユーザーの名前が AdminSammy であり、その認証データベースが admin
. 異なる場合は、次のコマンドでこれらの詳細を変更して、独自の設定を反映させてください。
- mongo -u AdminSammy -p --authenticationDatabase admin
インストール中に設定したパスワードを入力して、シェルにアクセスします。 パスワードを入力すると、プロンプトが大なり記号に変わります(>
).
注:新しい接続では、MongoDBシェルは自動的に test
デフォルトではデータベース。 このデータベースを安全に使用して、MongoDBとMongoDBシェルを試すことができます。
または、別のデータベースに切り替えて、このチュートリアルに記載されているすべてのサンプルコマンドを実行することもできます。 別のデータベースに切り替えるには、 use
コマンドの後にデータベースの名前を続けます。
- use database_name
インデックスがどのように機能するかを説明するために、さまざまなタイプの複数のフィールドを持つドキュメントのコレクションが必要になります。 世界で最も高い5つの山のサンプルコレクションを使用します。 以下は、エベレストを表すサンプルドキュメントです。
{
"name": "Everest",
"height": 8848,
"location": ["Nepal", "China"],
"ascents": {
"first": {
"year": 1953,
},
"first_winter": {
"year": 1980,
},
"total": 5656,
}
}
このドキュメントには、次の情報が含まれています。
name
:ピークの名前。height
:ピークの標高(メートル単位)。location
:山が位置する国。 このフィールドには、複数の国にある山を許可するための配列として値が格納されます。ascents
:このフィールドの値は別のドキュメントです。 あるドキュメントがこのように別のドキュメント内に保存されている場合、それは埋め込みまたはネストされたドキュメントと呼ばれます。 各ascents
文書は、与えられた山の成功した登りを説明しています。 具体的には、それぞれascents
ドキュメントに含まれているtotal
与えられた各ピークの成功した上昇の総数をリストするフィールド。 さらに、これらのネストされたドキュメントのそれぞれには、値がネストされたドキュメントでもある2つのフィールドが含まれています。first
:このフィールドの値は、1つのフィールドを含むネストされたドキュメントです。year
、これは最初の全体的に成功した上昇の年を表します。first_winter
:このフィールドの値はネストされたドキュメントであり、year
フィールド。その値は、指定された山の最初の冬の登山に成功した年を表します。
次を実行します insertMany()
MongoDBシェルのメソッドを使用して、という名前のコレクションを同時に作成します peaks
5つのサンプルドキュメントを挿入します。 これらの文書は、世界で最も高い5つの山頂について説明しています。
- db.peaks.insertMany([
- {
- "name": "Everest",
- "height": 8848,
- "location": ["Nepal", "China"],
- "ascents": {
- "first": {
- "year": 1953
- },
- "first_winter": {
- "year": 1980
- },
- "total": 5656
- }
- },
- {
- "name": "K2",
- "height": 8611,
- "location": ["Pakistan", "China"],
- "ascents": {
- "first": {
- "year": 1954
- },
- "first_winter": {
- "year": 1921
- },
- "total": 306
- }
- },
- {
- "name": "Kangchenjunga",
- "height": 8586,
- "location": ["Nepal", "India"],
- "ascents": {
- "first": {
- "year": 1955
- },
- "first_winter": {
- "year": 1986
- },
- "total": 283
- }
- },
- {
- "name": "Lhotse",
- "height": 8516,
- "location": ["Nepal", "China"],
- "ascents": {
- "first": {
- "year": 1956
- },
- "first_winter": {
- "year": 1988
- },
- "total": 461
- }
- },
- {
- "name": "Makalu",
- "height": 8485,
- "location": ["China", "Nepal"],
- "ascents": {
- "first": {
- "year": 1955
- },
- "first_winter": {
- "year": 2009
- },
- "total": 361
- }
- }
- ])
出力には、新しく挿入されたオブジェクトに割り当てられたオブジェクト識別子のリストが含まれます。
Output{
"acknowledged" : true,
"insertedIds" : [
ObjectId("61212a8300c8304536a86b2f"),
ObjectId("61212a8300c8304536a86b30"),
ObjectId("61212a8300c8304536a86b31"),
ObjectId("61212a8300c8304536a86b32"),
ObjectId("61212a8300c8304536a86b33")
]
}
を実行すると、ドキュメントが正しく挿入されたことを確認できます。 find()
すべてのドキュメントを取得する引数のないメソッド:
- db.peaks.find()
Output{ "_id" : ObjectId("61212a8300c8304536a86b2f"), "name" : "Everest", "height" : 8848, "location" : [ "Nepal", "China" ], "ascents" : { "first" : { "year" : 1953 }, "first_winter" : { "year" : 1980 }, "total" : 5656 } }
...
このサンプルコレクションは、インデックスのパフォーマンスへの影響またはインデックスの欠如を直接説明するのに十分な大きさではないことに注意してください。 ただし、このガイドでは、MongoDBがインデックスを使用して、データベースエンジンによって報告されたクエリの詳細を強調表示することにより、トラバースされるドキュメントの量を制限する方法について概説します。
サンプルデータが揃ったら、次のステップに進んで、単一のフィールドに基づいてインデックスを作成する方法を学ぶことができます。
ステップ2—単一フィールドインデックスの作成とインデックス使用の評価
この手順では、単一のフィールドインデックスを作成して、フィルタリング条件の一部としてそのフィールドを使用してデータをフィルタリングするドキュメントクエリを高速化する方法について説明します。 また、MongoDBがインデックスを使用してクエリのパフォーマンスを向上させたのか、代わりに完全なコレクションスキャンを使用したのかを確認する方法についても概説します。
まず、次のクエリを実行します。 通常、クエリドキュメント { "height": { $gt: 8700 } }
このクエリにより、山頂を説明するドキュメントが取得されます。 height
8700より大きい値。 ただし、この操作には explain(executionStats)
メソッド。代わりに、クエリが実行方法に関する情報を返します。 まだインデックスを作成していないため、これにより、インデックスを使用するクエリのパフォーマンスと比較するために使用できるベンチマークが提供されます。
- db.peaks.find(
- { "height": { $gt: 8700 } }
- ).explain("executionStats")
この操作は多くの情報を返します。 次の出力例では、このチュートリアルの目的にとって重要ではないいくつかの行が削除されています。
Output{
"queryPlanner" : {
. . .
"winningPlan" : {
"stage" : "COLLSCAN",
. . .
},
},
. . .
"executionStats" : {
. . .
"nReturned" : 1,
"executionTimeMillis" : 0,
"totalKeysExamined" : 0,
"totalDocsExamined" : 5,
. . .
},
. . .
}
この出力で返される次のフィールドは、インデックスがどのように機能するかを理解するのに特に関係があります。
winningPlan
:内のこのドキュメントqueryPlanner
セクションでは、MongoDBがクエリの実行を決定した方法について説明します。 クエリの種類に応じて、winningPlan
異なる場合がありますが、ここで注意すべき重要な点はCOLLSCAN
. この値が存在するということは、MongoDBが、要求されたドキュメントを見つけるための支援なしに、完全なコレクションを実行する必要があることを意味します。nReturned
:この値は、特定のクエリによって返されたドキュメントの数を示します。 ここでは、1つの山のピークだけがクエリに一致します。executionTimeMillis
:この値は実行時間を表します。 このような小さなコレクションでは、その重要性はごくわずかです。 ただし、より大規模またはより複雑なコレクションに対するクエリのパフォーマンスを分析する場合は、覚えておくことが重要な指標です。totalKeysExamined
:これは、MongoDBが要求されたドキュメントを見つけるためにチェックしたindexエントリの数を示します。 コレクションスキャンが使用され、まだインデックスを作成していないため、値は次のようになります。0
.totalDocsExamined
:この値は、MongoDBがコレクションから読み取る必要のあるドキュメントの数を示します。 MongoDBはコレクションスキャンを実行したため、その値は次のようになります。5
、コレクション内のすべてのドキュメントの総数。 コレクションが大きいほど、インデックスが使用されていない場合のこのフィールドの値は大きくなります。
調査されたドキュメントの総数と返されたドキュメントの数の不一致に注意してください。MongoDBは1つを返すために、5つのドキュメントを検査する必要がありました。
このチュートリアルでは、後のセクションでこれらの値を参照して、インデックスがクエリの実行方法にどのように影響するかを分析します。
そのために、にインデックスを作成します height
のフィールド peaks
を使用したコレクション createIndex()
方法。 このメソッドは、作成するインデックスを記述したJSONドキュメントを受け入れます。 この例では、単一のフィールドインデックスを作成します。これは、ドキュメントに単一のキーが含まれていることを意味します(height
この例では)使用するフィールドに対して。 このキーはどちらかを受け入れます 1
また -1
値として。 これらの値は、インデックスの並べ替え順序を示します。 1
昇順を示し、 -1
降順を示す:
- db.peaks.createIndex( { "height": 1 } )
注:単一フィールドインデックスでは、インデックス構造を両方向に効率的にトラバースできるため、順序は重要ではありません。 ステップ4で説明されているように、複数のフィールドに基づく複合インデックスでは、インデックスフィールドの順序を選択することがより重要になります。
MongoDBは、コレクションに現在定義されているインデックスの数と、それが以前の状態とどのように異なるかを示す確認を返します。
Output{
"createdCollectionAutomatically" : false,
"numIndexesBefore" : 1,
"numIndexesAfter" : 2,
"ok" : 1
}
ここで、前に実行したのと同じクエリを実行してみてください。 ただし、今回は、 explain("executionStats")
インデックスが設定されているため、メソッドは異なります。
- db.peaks.find(
- { "height": { $gt: 8700 } }
- ).explain("executionStats")
Output{
"queryPlanner" : {
. . .
"winningPlan" : {
. . .
"inputStage" : {
"stage" : "IXSCAN",
. . .
"indexName" : "height_1",
. . .
}
},
. . .
},
"executionStats" : {
. . .
"nReturned" : 1,
"executionTimeMillis" : 0,
"totalKeysExamined" : 1,
"totalDocsExamined" : 1,
. . .
},
. . .
}
注意してください winningPlan
表示されなくなりました COLLSCAN
. その代わり、 IXSCAN
が存在し、インデックスがクエリ実行の一部として使用されたことを示します。 MongoDBは、どのインデックスが使用されたかについても通知します。 indexName
価値。 デフォルトでは、MongoDBは、インデックスがバインドされ、順序が適用されるフィールド名からインデックス名を作成します。 から { "height": 1 }
、MongoDBは自動的に名前を生成しました height_1
.
最も重要な変更は executionStats
セクション。 繰り返しになりますが、このクエリは、で示されるように、単一のドキュメントのみを返しました。 nReturned
. ただし、今回は totalDocsExamined
たった1です。 これは、データベースがクエリを満たすためにコレクションから1つのドキュメントのみを取得したことを意味します。 The totalKeysExamined
結果をコンパイルするのに十分な情報を提供したため、インデックスが1回だけチェックされたことを示しています。
このインデックスを作成することで、MongoDBが検査する必要のあるドキュメントの数を5から1に減らし、5分の1に減らしました。 の場合 peaks
コレクションには何千ものエントリが含まれていたため、インデックスを使用した場合の影響はさらに明白になります。
ステップ3—一意のインデックスを作成する
MongoDBでは、2つのドキュメントが同じである場合、それらをコレクションに挿入することはできません。 _id
値。 これは、データベースが自動的に単一フィールドインデックスを維持するためです。 _id
ドキュメントの検索を高速化するのに役立つだけでなく、 _id
フィールド値。 この手順では、インデックスを作成して、特定のフィールドの値がコレクション内のすべてのドキュメントで一意になるようにする方法について説明します。
説明のために、以下を実行します createIndex()
方法。 このコマンドの構文は、前の手順で使用した構文と似ていますが、今回は2番目のパラメーターがに渡される点が異なります。 createIndex()
インデックスの追加設定があります。 The { "unique": true }
作成されたインデックスが指定されたフィールドの値を保証することを示します(name
)繰り返すことはできません:
- db.peaks.createIndex( { "name": 1 }, { "unique": true } )
もう一度、MongoDBはインデックスが正常に作成されたことを確認します。
Output{
"createdCollectionAutomatically" : false,
"numIndexesBefore" : 2,
"numIndexesAfter" : 3,
"ok" : 1
}
次に、インデックスがその主な目的を果たしているかどうかを確認し、コレクションスキャンを回避することで、山の名前に対してクエリをより高速に実行します。 これを行うには、次の等式クエリを実行します。 explain("executionStats")
方法:
- db.peaks.find(
- { "name": "Everest" }
- ).explain("executionStats")
返されるクエリプランは IXSCAN
前のステップの山の高さのクエリと同様に、新しく作成されたインデックスを使用した戦略:
Output{
"queryPlanner" : {
. . .
"winningPlan" : {
. . .
"inputStage" : {
"stage" : "IXSCAN",
. . .
"indexName" : "name_1",
. . .
}
},
. . .
},
. . .
}
次に、山を表す2番目のドキュメントを追加できるかどうかを確認します。 インデックスが設定されたので、コレクションにエベレスト。 以下を実行してこれを行います insertOne()
方法:
- db.peaks.insertOne({
- "name": "Everest",
- "height": 9200,
- "location": ["India"],
- "ascents": {
- "first": {
- "year": 2020
- },
- "first_winter": {
- "year": 2021
- },
- "total": 2
- }
- })
MongoDBはドキュメントを作成せず、代わりにエラーメッセージを返します。
OutputWriteError({
"index" : 0,
"code" : 11000,
"errmsg" : "E11000 duplicate key error collection: test.peaks index: name_1 dup key: { name: \"Everest\" }",
"op" : {
. . .
これ duplicatye key error
メッセージは name_1
インデックス。このフィールドに一意性の制約を適用していることを示します。
これで、特定のフィールドに重複する値が含まれないようにするための一意のインデックスを作成する方法を学習しました。 埋め込まれたドキュメントでインデックスを使用する方法を学ぶために読み続けてください。
ステップ4—埋め込みフィールドにインデックスを作成する
インデックスを持たないネストされたドキュメント内のフィールドを使用してコレクションをクエリする場合は常に、MongoDBはコレクションからすべてのドキュメントを取得するだけでなく、ネストされた各ドキュメントをトラバースする必要があります。
例として、次のクエリを実行します。 これにより、次のようなドキュメントが返されます total
—内にネストされたフィールド ascents
の各ドキュメントで見つかったドキュメント peaks
コレクション— 300より大きく、結果を降順で並べ替えます。
- db.peaks.find(
- { "ascents.total": { $gt: 300 } }
- ).sort({ "ascents.total": -1 })
このクエリは、コレクションから4つのピークを返します。 エベレストが最も上昇のピークであり、ローツェ、マカルー、K2がそれに続きます。
{ "_id" : ObjectId("61212a8300c8304536a86b2f"), "name" : "Everest", "height" : 8848, "location" : [ "Nepal", "China" ], "ascents" : { "first" : { "year" : 1953 }, "first_winter" : { "year" : 1980 }, "total" : 5656 } }
{ "_id" : ObjectId("61212a8300c8304536a86b32"), "name" : "Lhotse", "height" : 8516, "location" : [ "Nepal", "China" ], "ascents" : { "first" : { "year" : 1956 }, "first_winter" : { "year" : 1988 }, "total" : 461 } }
{ "_id" : ObjectId("61212a8300c8304536a86b33"), "name" : "Makalu", "height" : 8485, "location" : [ "China", "Nepal" ], "ascents" : { "first" : { "year" : 1955 }, "first_winter" : { "year" : 2009 }, "total" : 361 } }
{ "_id" : ObjectId("61212a8300c8304536a86b30"), "name" : "K2", "height" : 8611, "location" : [ "Pakistan", "China" ], "ascents" : { "first" : { "year" : 1954 }, "first_winter" : { "year" : 1921 }, "total" : 306 } }
ここで同じクエリを実行しますが、 explain("executionStats")
以前に使用された方法:
- db.peaks.find(
- { "ascents.total": { $gt: 300 } }
- ).sort({ "ascents.total": -1 }).explain("executionStats")
として COLLSCAN
出力のこのセクションの値は、MongoDBが完全なコレクションスキャンに頼り、からのすべてのドキュメントをトラバースしたことを示しています。 peaks
それらをクエリ条件と比較するためのコレクション:
Output{
. . .
"winningPlan" : {
"stage" : "COLLSCAN",
. . .
},
. . .
}
このコレクションには5つのエントリしかないため、インデックスがなくてもパフォーマンスに大きな影響はなく、このクエリはすぐに実行されました。 ただし、データベースに格納されているドキュメントが複雑になるほど、クエリのパフォーマンスへの影響が大きくなる可能性があります。 この手順では、この問題を軽減するために、埋め込みドキュメント内のフィールドに単一フィールドインデックスを作成する方法の概要を説明します。
MongoDBがこのクエリを実行できるように、インデックスを作成しましょう。 total
内のフィールド ascents
資料。 なぜなら total
フィールドは内にネストされています ascents
、指定することはできません total
このインデックスを作成するときのフィールド名として。 代わりに、MongoDBは、ネストされたドキュメントのフィールドにアクセスするためのドット表記を提供します。 参照するには total
内部のフィールド ascents
ネストされたドキュメント、あなたは使用することができます ascents.total
このような表記法:
- db.peaks.createIndex( { "ascents.total": 1 } )
MongoDBは、4つのインデックスが定義されたことを通知する成功メッセージで応答します。
{
"createdCollectionAutomatically" : false,
"numIndexesBefore" : 3,
"numIndexesAfter" : 4,
"ok" : 1
}
注:このチュートリアルでは、さまざまなタイプのインデックスを使用する方法を示すために、ステップごとにインデックスを追加します。 ただし、インデックスの数が多すぎると、少なすぎるのと同じくらいパフォーマンスが低下する可能性があることに注意してください。
データベース内のすべてのインデックスについて、MongoDBは、新しいドキュメントがコレクションに挿入されたり、変更されたりするたびに、それぞれを適切に更新する必要があります。 多くのインデックスを持つことによるパフォーマンスの低下は、クエリ速度の向上を通じてそれらが提供する利点に対抗する可能性があります。 頻繁にクエリされるフィールド、またはパフォーマンスに最も影響を与えるフィールドにのみインデックスを追加してください。
前のクエリをもう一度実行して、インデックスがMongoDBが完全なコレクションスキャンの実行を回避するのに役立ったかどうかを確認します。
- db.peaks.find(
- { "ascents.total": { $gt: 300 } }
- ).sort({ "ascents.total": -1 }).explain("executionStats")
Output{
"queryPlanner" : {
. . .
"winningPlan" : {
. . .
"inputStage" : {
"stage" : "IXSCAN",
. . .
"indexName" : "ascents.total_-1",
. . .
}
},
. . .
},
"executionStats" : {
. . .
"nReturned" : 4,
"executionTimeMillis" : 0,
"totalKeysExamined" : 4,
"totalDocsExamined" : 4,
. . .
"direction" : "backward",
. . .
},
. . .
}
今注目してください IXSCAN
新しく作成されたものに対して使用されます ascents.total_-1
インデックス、および4つのドキュメントのみが調べられました。 これは、インデックスで返され、調べられるドキュメントの数と同じであるため、クエリを完了するために追加のドキュメントが取得されていません。
direction
、の別のフィールド executionStats
セクションは、MongoDBがインデックスをトラバースすることを決定した方向を示します。 インデックスは昇順で作成されたため、 { "ascents.total": 1 }
構文、およびクエリが降順でソートされた山頂を要求したため、データベースエンジンは逆方向に進むことを決定しました。 インデックスの一部であるフィールドに基づいて特定の順序でドキュメントを取得する場合、MongoDBはインデックスを使用して、ドキュメントを完全に取得した後にドキュメントをさらに並べ替える必要なしに、最終的な順序を提供します。
ステップ5—複合フィールドインデックスを作成する
このガイドのこれまでの例は、インデックスを使用する利点を理解するのに役立ちますが、実際のアプリケーションで使用されるドキュメントフィルタリングクエリがこれほど単純なことはめったにありません。 このステップでは、MongoDBが複数のフィールドでクエリを実行するときにインデックスを使用する方法と、複合フィールドインデックスを使用してそのようなクエリを具体的にターゲットにする方法について説明します。
に単一のフィールドインデックスを作成したときの手順2を思い出してください。 height
より効率的にクエリを実行するためのフィールド peaks
最高の山頂を見つけるためのコレクション。 このインデックスを設定したら、MongoDBが同様のクエリを実行する方法を分析してみましょう。 1990年以降に最初の冬の登山が発生した高さ8600メートル未満の山を見つけてみてください。
- db.peaks.find(
- {
- "ascents.first_winter.year": { $gt: 1990 },
- "height": { $lt: 8600 }
- }
- ).sort({ "height": -1 })
単一の山—マカルー—だけがこれらの条件の両方を満たします:
Output{ "_id" : ObjectId("61212a8300c8304536a86b33"), "name" : "Makalu", "height" : 8485, "location" : [ "China", "Nepal" ], "ascents" : { "first" : { "year" : 1955 }, "first_winter" : { "year" : 2009 }, "total" : 361 } }
次に、 explaion("executionStats")
MongoDBがこのクエリをどのように実行したかを見つけるメソッド:
- db.peaks.find(
- {
- "ascents.first_winter.year": { $gt: 1990 },
- "height": { $lt: 8600 }
- }
- ).sort({ "height": -1 }).explain("executionStats")
最初の冬の上昇日に影響を与える可能性のあるインデックスはありませんが、MongoDBは、完全なコレクションスキャンを実行する代わりに、以前に作成されたインデックスを使用しました。
Output{
"queryPlanner" : {
. . .
"winningPlan" : {
"stage" : "IXSCAN",
. . .
"indexName" : "height_1",
. . .
}
},
. . .
},
"executionStats" : {
. . .
"nReturned" : 1,
"executionTimeMillis" : 0,
"totalKeysExamined" : 3,
"totalDocsExamined" : 3,
. . .
},
. . .
}
今回は、以前のインデックスに基づくクエリの実行とは異なり、 nReturned
返されるドキュメントの数を示す値は、両方とは異なります totalKeysExamined
と totalDocsExamined
. MongoDBは、 height
結果を5から3に絞り込むためのフィールドですが、最初の冬の上昇日を確認するために残りのドキュメントをスキャンする必要がありました。
インデックスがクエリの一部でのみ使用可能な場合、MongoDBはそれを使用して、コレクションスキャンを実行する前に、最初に結果を絞り込みます。 残りのクエリを満たすために、最初にフィルタリングしたドキュメントのリストのみをトラバースします。
多くの場合、これで十分です。 最も一般的なクエリが単一のインデックス付きフィールドを調べ、追加のフィルタリングをたまにしか実行する必要がない場合は、通常、単一のフィールドインデックスを使用するだけで十分です。 ただし、複数のフィールドに対するクエリが一般的である場合は、これらすべてのフィールドにまたがるインデックスを定義して、追加のスキャンを実行する必要がないことを確認すると便利な場合があります。
最初の冬の登りと高さに関連する条件を満たす山のピークをデータベースにクエリして、パフォーマンスの問題になり、インデックスを作成することでメリットが得られると想像してみてください。 これらのフィールドフィールドの両方に基づいてインデックスを作成するには、次のコマンドを実行します createIndex(0)
方法:
- db.peaks.createIndex(
- {
- "ascents.first_winter.year": 1,
- "height": -1
- }
- )
この操作の構文は単一フィールドのインデックス作成に似ていますが、今回は両方のフィールドがインデックス定義オブジェクトにリストされていることに注意してください。 インデックスは、ピークの最初の冬の上昇に関して上昇し、それらの高さに関して下降するように作成されます。
MongoDBは、インデックスが正常に作成されたことを確認します。
Output{
"createdCollectionAutomatically" : false,
"numIndexesBefore" : 4,
"numIndexesAfter" : 5,
"ok" : 1
}
単一フィールドインデックスを使用すると、データベースエンジンはインデックスを順方向または逆方向に自由にトラバースできます。 ただし、複合インデックスでは、これが常に当てはまるとは限りません。 フィールドの組み合わせの特定の並べ替え順序がより頻繁に照会される場合は、パフォーマンスをさらに向上させて、その順序をインデックス定義に含めることができます。 その後、MongoDBは、返されたドキュメントのリストで追加の並べ替えを行うのではなく、インデックスを直接使用して要求された順序を満たします。
前のクエリをもう一度実行して、クエリの実行方法に変更があったかどうかをテストします。
- db.peaks.find(
- {
- "ascents.first_winter.year": { $gt: 1990 },
- "height": { $lt: 8600 }
- }
- ).sort({ "height": -1 }).explain("executionStats")
今回もクエリはインデックススキャンを使用しましたが、インデックスは異なります。 さて、 ascents.first_winter.year_1_height_-1
作成したばかりのインデックスは、以前に使用したものよりも選択されます height_1
索引:
Output{
"queryPlanner" : {
. . .
"winningPlan" : {
"stage" : "IXSCAN",
. . .
"indexName" : "ascents.first_winter.year_1_height_-1",
. . .
}
},
. . .
},
"executionStats" : {
. . .
"nReturned" : 1,
"executionTimeMillis" : 0,
"totalKeysExamined" : 1,
"totalDocsExamined" : 1,
. . .
},
. . .
}
重要な違いは executionStats
. 新しいインデックスでは、結果を絞り込むためにさらにドキュメントをスキャンする必要がある3つのドキュメントとは対照的に、1つのドキュメントがインデックスから直接調べられてから返されました。 これがより大きなコレクションである場合、新しい複合インデックスと、さらにフィルタリングを行う単一フィールドインデックスを使用することの違いは、さらに顕著になります。
複数のフィールドにまたがるインデックスを作成する方法を学習したので、マルチキーインデックスとその使用方法について学習することができます。
ステップ6—マルチキーインデックスを作成する
前の例では、インデックスで使用されるフィールドには、高さ、年、名前などの単一の値が格納されていました。 このような場合、MongoDBはフィールド値をインデックスキーとして直接保存し、インデックスをすばやくトラバースできるようにします。 この手順では、インデックスの作成に使用されるフィールドが配列などの複数の値を格納するフィールドである場合のMongoDBの動作の概要を説明します。
まず、ネパールにあるコレクション内のすべての山を見つけてみてください。
- db.peaks.find(
- { "location": "Nepal" }
- )
4つのピークが返されます。
Output{ "_id" : ObjectId("61212a8300c8304536a86b2f"), "name" : "Everest", "height" : 8848, "location" : [ "Nepal", "China" ], "ascents" : { "first" : { "year" : 1953 }, "first_winter" : { "year" : 1980 }, "total" : 5656 } }
{ "_id" : ObjectId("61212a8300c8304536a86b31"), "name" : "Kangchenjunga", "height" : 8586, "location" : [ "Nepal", "India" ], "ascents" : { "first" : { "year" : 1955 }, "first_winter" : { "year" : 1986 }, "total" : 283 } }
{ "_id" : ObjectId("61212a8300c8304536a86b32"), "name" : "Lhotse", "height" : 8516, "location" : [ "Nepal", "China" ], "ascents" : { "first" : { "year" : 1956 }, "first_winter" : { "year" : 1988 }, "total" : 461 } }
{ "_id" : ObjectId("61212a8300c8304536a86b33"), "name" : "Makalu", "height" : 8485, "location" : [ "China", "Nepal" ], "ascents" : { "first" : { "year" : 1955 }, "first_winter" : { "year" : 2009 }, "total" : 361 } }
ネパールでは、これらのピークはいずれものみではないことに注意してください。 これらの4つのピークはそれぞれ、 location
フィールド。これらはすべて複数の値の配列です。 さらに、これらの値はさまざまな順序で表示される可能性があります。 たとえば、ローツェは [ "Nepal", "China" ]
、マカルーは [ "China", "Nepal" ]
.
にまたがる利用可能なインデックスがないため location
フィールドでは、MongoDBは現在、そのクエリを実行するために完全なコレクションスキャンを実行します。 の新しいインデックスを作成しましょう location
分野:
- db.peaks.createIndex( { "location": 1 } )
この構文は、他の単一フィールドインデックスと変わらないことに注意してください。 MongoDBは成功メッセージを返し、インデックスを使用できるようになります。
Output{
"createdCollectionAutomatically" : false,
"numIndexesBefore" : 5,
"numIndexesAfter" : 6,
"ok" : 1
}
これで、のインデックスが作成されました。 location
フィールドで、前のクエリを再度実行します。 explain("executionStats")
それがどのように実行されるかを理解する方法:
- db.peaks.find(
- { "location": "Nepal" }
- ).explain("executionStats")
結果の出力は、MongoDBが新しく作成されたものを参照して、戦略としてインデックススキャンを使用したことを示しています location_1
索引:
Output{
"queryPlanner" : {
. . .
"winningPlan" : {
. . .
"inputStage" : {
"stage" : "IXSCAN",
. . .
"indexName" : "location_1",
"isMultiKey" : true,
. . .
}
},
. . .
},
"executionStats" : {
. . .
"nReturned" : 4,
"executionTimeMillis" : 0,
"totalKeysExamined" : 4,
"totalDocsExamined" : 4,
. . .
}
. . .
}
返されたドキュメントの数は、検査されたインデックスキーと検査されたドキュメントの総数と一致します。 これは、インデックスがクエリの唯一の情報源として使用されたことを意味します。 フィールド値が複数の値の配列であり、クエリが場所の1つがネパールと一致する山を要求した場合、どうしてそれが可能でしたか?
に注意してください isMultiKey
としてリストされているプロパティ true
出力で。 MongoDBは、 location
分野。 配列を保持するフィールドのインデックスを作成すると、MongoDBはマルチキーインデックスを作成する必要があると自動的に判断し、これらの配列のすべての要素に対して個別のインデックスエントリを作成します。
したがって、 location
配列を格納するフィールド [ "China", "Nepal" ]
、2つの別々のインデックスエントリが同じドキュメントに表示されます。1つは中国用、もう1つはネパール用です。 このようにして、クエリが配列の内容に対して部分的な一致を要求した場合でも、MongoDBはインデックスを効率的に使用できます。
ステップ7—コレクションのインデックスの一覧表示と削除
前の手順では、さまざまなタイプのインデックスを作成する方法を学習しました。 データベースが大きくなったり、要件が変更されたりした場合は、定義されているインデックスを把握し、不要なインデックスを削除できることが重要です。 使用できなくなったインデックスは、データベースのパフォーマンスに悪影響を与える可能性があります。これは、データを追加または変更するたびにMongoDBがインデックスを維持する必要があるためです。
で定義したすべてのインデックスを一覧表示するには peaks
このチュートリアル全体のコレクションでは、 getIndexes()
方法:
- db.peaks.getIndexes()
MongoDBは、インデックスの性質を説明し、名前をリストしたインデックスのリストを返します。
Output[
{
"v" : 2,
"key" : {
"_id" : 1
},
"name" : "_id_"
},
{
"v" : 2,
"key" : {
"height" : 1
},
"name" : "height_1"
},
{
"v" : 2,
"unique" : true,
"key" : {
"name" : 1
},
"name" : "name_1"
},
{
"v" : 2,
"key" : {
"ascents.total" : 1
},
"name" : "ascents.total_1"
},
{
"v" : 2,
"key" : {
"ascents.first_winter.year" : 1,
"height" : -1
},
"name" : "ascents.first_winter.year_1_height_-1"
},
{
"v" : 2,
"key" : {
"location" : 1
},
"name" : "location_1"
}
]
このチュートリアル全体で、6つのインデックスをまとめて定義しました。 それぞれについて、 key
プロパティは、インデックスが以前に作成された方法と一致するインデックス定義を一覧表示します。 インデックスごとに、 name
プロパティには、インデックスの作成時に自動的に生成されたMongoDBという名前が含まれています。
既存のインデックスを削除するには、これらのプロパティのいずれかを dropIndex()
方法。 次の例では、 height_1
その内容の定義を使用してインデックスを作成します。
- db.peaks.dropIndex( { "height": 1 } )
以来 { "height": 1 }
上の単一フィールドインデックスに一致します height
名前付き height_1
、MongoDBはそのインデックスを削除し、これを削除する前にインデックスがいくつあったかを示す成功メッセージで応答します。
Output{ "nIndexesWas" : 6, "ok" : 1 }
複合インデックスの場合のように、インデックス定義がより複雑な場合、削除するインデックスを指定するこの方法は扱いにくくなる可能性があります。 別の方法として、インデックスの名前を使用してインデックスを削除できます。 手順5で最初の冬の上昇と高さで作成されたインデックスをその名前を使用して削除するには、次の操作を実行します。
- db.peaks.dropIndex("ascents.first_winter.year_1_height_-1")
もう一度、MongoDBはインデックスを削除し、成功メッセージを返します。
Output{ "nIndexesWas" : 5, "ok" : 1 }
を呼び出すことにより、これら2つのインデックスがコレクションインデックスのリストから実際に削除されたことを確認できます。 getIndexes()
また:
- db.peaks.getIndexes()
今回は、残りの4つのインデックスのみがリストされています。
Output[
{
"v" : 2,
"key" : {
"_id" : 1
},
"name" : "_id_"
},
{
"v" : 2,
"unique" : true,
"key" : {
"name" : 1
},
"name" : "name_1"
},
{
"v" : 2,
"key" : {
"ascents.total" : 1
},
"name" : "ascents.total_1"
},
{
"v" : 2,
"key" : {
"location" : 1
},
"name" : "location_1"
}
]
最後に、MongoDBの既存のインデックスを変更することはできないことに注意してください。 インデックスを変更する必要がある場合は、最初にそのインデックスを削除して、新しいインデックスを作成する必要があります。
結論
この記事を読むことで、インデックスの概念に慣れることができます。これは、クエリの実行中にMongoDBが分析する必要のあるデータの量を減らすことで、クエリのパフォーマンスを向上させることができる特別なデータ構造です。 単一フィールド、複合、およびマルチキーインデックスを作成する方法と、それらの存在がクエリの実行に影響を与えるかどうかを確認する方法を学習しました。 また、既存のインデックスを一覧表示し、不要なインデックスを削除する方法も学習しました。
このチュートリアルでは、ビジー状態のデータベースでクエリのパフォーマンスを向上させるためにMongoDBが提供するインデックス機能のサブセットについてのみ説明しました。 公式の公式のMongoDBドキュメントを調べて、インデックス作成と、それがさまざまなシナリオでのパフォーマンスにどのように影響するかについて学ぶことをお勧めします。