シェア

はじめに

百科事典と同様に、データベースはアクセス可能な豊富な情報の宝庫です。百科事典で特定の情報を見つけるには、探しているものが見つかるまで全ページを丹念に調べる必要があります。この非効率性こそ、百科事典にインデックスがある理由であり、インデックスは探している情報が載っている正確なページを教えてくれます。

データベースのインデックスも同様に、情報をより効率的に見つけるための適切な場所を示してくれます。MongoDBでは、「本全体を検索する」インデックスのないクエリは、コレクションスキャンと呼ばれます。

インデックスは、探しているものを見つけるためにデータベース全体をスキャンする必要がないように、データにアクセスするためのショートカットと考えることができます。この記事では、MongoDBのインデックスを紹介し、いつ使用すべきか、そしてそれらを管理する方法について説明します。

Database Indexes

いつインデックスを使用すべきか

百科事典のアナロジーを続けると、本のすべての単語にインデックス行を設けることを考えるかもしれません。インデックスを使用する方が常に速いのであれば、これは有益に思えます。しかし、想像できるように、インデックス内の単語の行数が増えるほど、本は大きくなります。ある時点で、すべての単語のインデックス作成に対応するために必要な本のサイズは非効率になります。「the」や「because」など、検索するのに「カバ」ほど有用でない単語がたくさんあります。

これは、MongoDBおよび一般的なデータベースのインデックスと同様です。確かに、クエリが使用する可能性のあるすべてのデータにインデックスがある方が速いですが、インデックス作成が必要ないデータも確かに存在します。本のサイズと同様に、データベースにインデックスを過剰に追加すると、スペースも占有し、適切に管理しないとデータベースの書き込み操作に悪影響を及ぼします。

インデックスは、クエリの選択基準として頻繁に使用される特定のデータへのアクセスを最適化するための非常に便利な方法です。いつインデックスを使用するかを知ることは重要であり、頻繁にクエリされるデータベースフィールドにインデックスを追加することで、データベースのサイズと書き込み効率に悪影響を与えることなく、読み取りを効率的に保つことができます。

インデックスの作成方法

インデックスが何であるか、そしていつ使用すべきかを理解できたので、インデックスを作成する方法について説明できます。

インデックス作成の恩恵を受ける可能性のあるフィールドを特定したら、MongoDBのcreateIndex()メソッドを使用します。基本的な構文は次のとおりです

db.COLLECTION_NAME.createIndex( { "FIELD_NAME": 1 } )

FIELD_NAMEは、インデックスを作成するフィールドの名前であり、1は昇順を指定します。

メソッドの使用例は次のようになります

db.mycoll.createIndex( { "country": 1 } )

createIndex()メソッドを使用して、次のようなカンマ区切りのリストを作成することにより、複数のフィールドにインデックスを作成することもできます

db.COLLECTION_NAME.createIndex( { "FIELD_NAME_1": 1, "FIELD_NAME_2": -1 } )

インデックスの表示方法

インデックスの作成を開始したら、データベースインスタンスに存在するインデックスを確認したい場合があります。MongoDBでは、getIndexes()メソッドを使用して、コレクション内のすべてのインデックスの説明を返すことができます。

コレクションのすべてのインデックスを表示するための基本的な構文は次のとおりです

db.COLLECTION_NAME.getIndexes()

前にインデックスを作成した例を使用すると、以下にメソッドとそれが返すものが表示されます。

db.mycoll.getIndexes()

戻り値:

[
{
"v" : 2,
"key" : {
"country" : 1
},
"name" : "country"
}
]

インデックス情報には、インデックスの作成に使用されたキーとオプションが含まれます。

インデックスのパフォーマンスを理解する方法

これで、コレクションにインデックスを作成して確認する機能ができたので、インデックスが期待どおりに機能しているかどうかを確認する必要があります。

例を始めるにあたり、約50.3kのドキュメントを持つsample_mflixデータベースとcommentsコレクションを使用します。これは、MongoDB Universityが提供する映画やテレビのコメントのデータストアをシミュレートしたサンプルコレクションです。

インデックスのパフォーマンスを理解するために、最初にインデックスがないクエリを実行します。次のクエリは、コレクション内のRamsay Boltonによって作成されたコメントである273個のドキュメントすべてを返します

db.comments.find( { "name" : "Ramsay Bolton" } )

ここで、MongoDBの説明プランをクエリに追加すると、このクエリのパフォーマンスが表示されます。

db.comments.find( { "name" : "Ramsay Bolton" } ).explain("executionStats")

これにより、次の結果が得られます

{
queryPlanner: {
plannerVersion: 1,
namespace: 'sample_mflix.comments',
indexFilterSet: false,
parsedQuery: { name: { '$eq': 'Ramsay Bolton' } },
winningPlan: {
stage: 'COLLSCAN',
filter: { name: { '$eq': 'Ramsay Bolton' } },
direction: 'forward'
},
rejectedPlans: []
},
executionStats: {
executionSuccess: true,
nReturned: 273,
executionTimeMillis: 23,
totalKeysExamined: 0,
totalDocsExamined: 50303,
executionStages: {
stage: 'COLLSCAN',
filter: { name: { '$eq': 'Ramsay Bolton' } },
nReturned: 273,
executionTimeMillisEstimate: 6,
works: 50305,
advanced: 273,
needTime: 50031,
needYield: 0,
saveState: 50,
restoreState: 50,
isEOF: 1,
direction: 'forward',
docsExamined: 50303
}
}
}

この出力で注目すべきいくつかの重要な結果があります。まず、winningPlanで、このクエリのstageCOLLSCANであることがわかります。これは、totalDocsExaminedが50,303、executionTimeMillisが23ミリ秒で、このクエリを完了するためにコレクションスキャンが発生したことを意味します。クエリはコレクション内のすべてのドキュメントを調べる必要があり、nReturnedがわずか273ドキュメントであっても23ミリ秒かかりました。23ミリ秒は大したことないように聞こえるかもしれませんが、100万件のドキュメントを格納するコレクションでは、これははるかに長くなる可能性があります。

nameに対するクエリが、このコレクションにアクセスするアプリケーションにとって反復的なパターンになる場合、このフィールドにインデックスを作成することをお勧めします。そうするには、次のように記述します

db.comments.createIndex( {"name":1} )

以前と同じ説明プランで同じクエリを実行すると

db.comments.find( { "name" : "Ramsay Bolton" } ).explain("executionStats")
{
queryPlanner: {
plannerVersion: 1,
namespace: 'sample_mflix.comments',
indexFilterSet: false,
parsedQuery: { name: { '$eq': 'Ramsay Bolton' } },
winningPlan: {
stage: 'FETCH',
inputStage: {
stage: 'IXSCAN',
keyPattern: { name: 1 },
indexName: 'name_1',
isMultiKey: false,
multiKeyPaths: { name: [] },
isUnique: false,
isSparse: false,
isPartial: false,
indexVersion: 2,
direction: 'forward',
indexBounds: { name: [ '["Ramsay Bolton", "Ramsay Bolton"]' ] }
}
},
rejectedPlans: []
},
executionStats: {
executionSuccess: true,
nReturned: 273,
executionTimeMillis: 0,
totalKeysExamined: 273,
totalDocsExamined: 273,
executionStages: {
stage: 'FETCH',
nReturned: 273,
executionTimeMillisEstimate: 0,
works: 274,
advanced: 273,
needTime: 0,
needYield: 0,
saveState: 0,
restoreState: 0,
isEOF: 1,
docsExamined: 273,
alreadyHasObj: 0,
inputStage: {
stage: 'IXSCAN',
nReturned: 273,
executionTimeMillisEstimate: 0,
works: 274,
advanced: 273,
needTime: 0,
needYield: 0,
saveState: 0,
restoreState: 0,
isEOF: 1,
keyPattern: { name: 1 },
indexName: 'name_1',
isMultiKey: false,
multiKeyPaths: { name: [] },
isUnique: false,
isSparse: false,
isPartial: false,
indexVersion: 2,
direction: 'forward',
indexBounds: { name: [ '["Ramsay Bolton", "Ramsay Bolton"]' ] },
keysExamined: 273,
seeks: 1,
dupsTested: 0,
dupsDropped: 0
}
}
}
}

インデックスなしのクエリと比較すると、winningPlan.inputstageIXSCANになり、インデックスが使用されたことがわかります。

さらに、totalDocsExaminedが、name"Ramsay Bolton"である273個のドキュメントのみになり、50,303個のドキュメント全体ではないことがわかります。この効率の向上は、特にexecutionTimeMillisが合計0ミリ秒になったことで確認できます。nameの新しいインデックスは、クエリが探しているデータを見つけるためにどこを探すべきかを正確に伝えました。

最も重要なクエリの説明プランを分析すると、インデックスのパフォーマンスが表示されるか、アプリケーションの効率を高めるためにインデックスを作成する必要がある場合が強調表示されます。

インデックスの削除方法

説明プランはインデックスの必要性を示す場合がありますが、逆のこともあります。たとえば、インデックスが不要になったり、パフォーマンスがそれほど向上しなかったりする場合は、スペースを確保したり、書き込みパフォーマンスを向上させたりするために、そのインデックスを削除することが最善の策となる場合があります。

コレクションのインデックスを削除するには、dropIndexes()メソッドを使用して、基本的な構文は次のとおりです

db.COLLECTION_NAME.dropIndex( { "FIELD_NAME": 1 } )

以前のcountryインデックスの例を削除したい場合は、次のように記述します

db.mycoll.dropIndex( { "country":1 } )

結論

このガイドでは、データベースを効率的にクエリすることで、アプリケーションのユーザーエクスペリエンスがどのように向上するかについて説明しました。さらに、分析やその他の社内業務にデータを使用する人は、より高速なパフォーマンスとデータベースの操作の容易さを実現できます。インデックスを作成する方法とインデックスの仕組みを知ることは、クエリ効率を達成するための鍵となります。

MongoDBでのインデックスの作成、分析、および削除の基本について説明しました。これらのインデックスの基礎知識は、MongoDBでのより高度なインデックス作成に進むための適切な基盤を提供します。

FAQ

2dインデックスは、2次元平面上の点として格納されたデータに使用します。これは、古いMongoDBバージョンでのレガシー座標ペアを対象としています。

2dインデックスは、2つのフィールドを参照できます。最初のフィールドはロケーションフィールドである必要があります。2d複合インデックスは、最初にロケーションフィールドで選択し、追加の基準でそれらの結果をフィルタリングするクエリを構築します。

小規模なコレクションでも大規模なコレクションでも、createIndex()メソッドを使用します。

大規模なコレクションでインデックスを作成する際に問題が発生している場合は、水平スケーリングを検討して、より管理しやすくすることをお勧めします。

MongoDBは、ローリングインデックスビルドアプローチも推奨しています。

MongoDBで埋め込みオブジェクトフィールドにインデックスを作成するには、ドット表記を使用できます。

たとえば、読んだ本を追跡するアプリがある場合、次のような構造のユーザーごとのコレクションがあるかもしれません

db.users.insertOne({
"first_name": "Alex",
"last_name": "Emerich",
"books": {
"first_book": {
"title": "Flights",
"author": "Olga Tokarczuk"
},
"second book": {
"title": "The Master and Margarita",
"author": "Mikhail Bulgakov"
},
"total": 2
}
})

埋め込みtotalフィールドにインデックスを作成するには、次のステートメントを記述します

db.users.createIndex( {"books.total": 1 } )

複合インデックスは、コレクションのドキュメント内の複数のフィールドへの参照を保持する単一のインデックス構造です。

複合インデックスを作成するための基本的な構文は次のとおりです

db.collection.createIndex( { <field1>: <type>, <field2>: <type2>, ... } )

ユニークインデックスは、テーブルの2つの行が、インデックス付きの列または列に重複した値を持たないようにします。MongoDBの場合、ドキュメントのフィールドまたはフィールドに重複した値があることです。

非ユニークインデックスはこの制限を課しません。

著者について
Alex Emerich

アレックス・エメリッヒ

アレックスは、典型的なバードウォッチング好きで、ヒップホップを愛する本の虫であり、データベースについて書くことも楽しんでいます。彼は現在ベルリンに住んでおり、レオポルド・ブルームのように目的もなく街を歩いている姿を見かけることができます。