インデックスを使用したクエリ結果の並べ替え
インデックスには順序付けられたレコードが含まれているため、MongoDB はソート フィールドを含むインデックスからソートの結果を取得できます。MongoDB では、ソートでクエリ述語と同じインデックスを使用する場合、ソート操作をサポートするために複数のインデックスを使用する場合があります。
MongoDB では、1 つまたは複数のインデックスを使用してソート順序を取得できない場合、データに対してブロッキングソート操作を実行する必要があります。ブロッキングソートは、結果を返す前に MongoDB が、ソートへのすべての入力ドキュメントを消費して処理する必要があることを示します。ブロッキングソートは、コレクションまたはデータベースに対する同時操作をブロックしません。
MongoDB 6.0以降では、サーバーがパイプライン実行ステージのために100 MB を超えるメモリを必要とする場合、そのクエリで { allowDiskUse: false }
が指定されていない限り、MongoDB では一時ファイルが自動的にディスクに書き込まれます。 サーバーがブロッキングソート操作に100メガバイトを超えるシステム メモリを必要とする場合、そのクエリが cursor.allowDiskUse()
を指定 しない限り 、MongoDB はエラーを返します。 詳細については、 allowDiskUseByDefault
を参照してください。
インデックスを使用するソート操作は、多くの場合、ブロッキングソートよりもパフォーマンスが優れています。
注意
マルチキー インデックスでインデックスされた配列フィールドに基づいてソートする場合、次の両方が当てはまらない限り、クエリプランにはクエリプランにはブロッキングソートステージが含まれます。
すべてのソートフィールドのインデックスの限界は
[MinKey, MaxKey]
です。マルチキー インデックスの付いたフィールドの境界には、ソート パターンと同じパス プレフィックスはありません。
単一のフィールド インデックスによるソート
昇順または降順のインデックスが 1 つのフィールドにある場合、そのフィールドのソート操作はどちらの方向でも実行できます。
たとえば、コレクション records
のフィールド a
に昇順のインデックスを作成します。
db.records.createIndex( { a: 1 } )
このインデックスは a
の昇順のソートをサポートします。
db.records.find().sort( { a: 1 } )
このインデックスは、インデックスを逆順にトラバースすることで、次のように a
の降順のソートもサポートできます。
db.records.find().sort( { a: -1 } )
複数のフィールドでソート
複数フィールドでのソートをサポートするには、複合インデックスを作成します。
インデックスのすべてまたは一部のキーに対してソートを指定できますが、ソート キーはインデックスに表示されるのと同じ順序で並べる必要があります。たとえば、インデックス キー パターン { a: 1, b: 1
}
は { a: 1, b: 1 }
でのソートをサポートしますが、{ b: 1, a:
1 }
でのソートはサポートしません。
クエリで複合インデックスを使用してソートするには、cursor.sort()
ドキュメント内のすべてのキーに対して指定されたソート方向がインデックス キー パターンと一致する、またはインデックス キー パターンの逆と一致する必要があります。たとえば、インデックス キー パターン { a: 1, b: -1 }
は、{ a: 1, b: -1 }
と { a: -1, b: 1 }
での並べ替えをサポートできますが、{ a: -1, b: -1 }
や {a: 1, b: 1}
での並べ替えはサポートできません。
ソートとインデックス プレフィックス
ソートキーがインデックス キーまたはインデックス プレフィックスに対応する場合、MongoDB はインデックスを使用してクエリ結果をソートできます。複合インデックスのプレフィックスは、インデックス キー パターンの先頭にある 1 つ以上のキーで構成されるサブセットです。
たとえば、data
コレクションに次のような複合インデックスを作成します。
db.data.createIndex( { a:1, b: 1, c: 1, d: 1 } )
次に、そのインデックスのプレフィックスを以下に示します。
{ a: 1 } { a: 1, b: 1 } { a: 1, b: 1, c: 1 }
以下のクエリとソート操作では、インデックス プレフィックスを使用して結果をソートします。これらの操作では、結果セットをメモリ内でソートする必要はありません。
例 | Index Prefix |
---|---|
db.data.find().sort( { a: 1 } ) | { a: 1 } |
db.data.find().sort( { a: -1 } ) | { a: 1 } |
db.data.find().sort( { a: 1, b: 1 } ) | { a: 1, b: 1 } |
db.data.find().sort( { a: -1, b: -1 } ) | { a: 1, b: 1 } |
db.data.find().sort( { a: 1, b: 1, c: 1 } ) | { a: 1, b: 1, c: 1 } |
db.data.find( { a: { $gt: 4 } } ).sort( { a: 1, b: 1 } ) | { a: 1, b: 1 } |
インデックスのプレフィックス キーがクエリ述語とソートの両方に出現する次の例を考えてみましょう。
db.data.find( { a: { $gt: 4 } } ).sort( { a: 1, b: 1 } )
このような場合、MongoDB はインデックスを使用して、ソートで指定された順序でドキュメントを検索できます。この例が示すように、クエリ述語のインデックス プレフィックスは、ソートのプレフィックスとは異なる場合があります。
ソートとインデックスのプレフィックス以外のサブセット
インデックスは、インデックス キー パターンのプレフィックス以外のサブセットに対するソート操作をサポートできます。そのためには、ソート キーに先行するすべてのプレフィックス キーに対する等価条件を、クエリに含める必要があります。
たとえば、コレクション data
が次のようなインデックスを持つとします。
{ a: 1, b: 1, c: 1, d: 1 }
次の操作では、インデックスを使用してソート順序を取得できます。
例 | Index Prefix |
---|---|
db.data.find( { a: 5 } ).sort( { b: 1, c: 1 } ) | { a: 1 , b: 1, c: 1 } |
db.data.find( { b: 3, a: 4 } ).sort( { c: 1 } ) | { a: 1, b: 1, c: 1 } |
db.data.find( { a: 5, b: { $lt: 3} } ).sort( { b: 1 } ) | { a: 1, b: 1 } |
最後の操作でわかるように、ソート サブセットに先行するインデックス フィールドのみが、クエリ ドキュメント内の等価条件を満たす必要があります。他のインデックス フィールドには他の条件を指定できます。
クエリで、ソート指定に先行する、またはソート指定と重複するインデックス プレフィックスに等価条件が指定されていない場合、操作ではインデックスが効率的に使用されません 。たとえば、次の操作では { c: 1 }
というソート ドキュメントが指定されますが、クエリ ドキュメントには先行するインデックス フィールド a
と b
の等価一致が含まれていません。
db.data.find( { a: { $gt: 2 } } ).sort( { c: 1 } ) db.data.find( { c: 5 } ).sort( { c: 1 } )
これらの操作では、インデックス { a: 1, b: 1,
c: 1, d: 1 }
が効率的に使用されず、ドキュメントを検索するためにインデックスが使用されない可能性もあります。
インデックスのソート順
インデックス作成されたドキュメントのコレクションでは、キー フィールドに複数のデータ型がある場合があります。
インデックスに複数のデータ型のキーがある場合、インデックスは BSON 型のソート順序でソートされます。
配列の比較では、次のようになります。
小なり比較または昇順ソートでは、BSON 型のソート順で配列の最小要素が比較されます。
大なり比較または降順ソートでは、BSON 型のソート順の逆順で配列の最大要素が比較されます。
値が 1 要素の配列(例:
[ 1 ]
)と配列以外のフィールド(例:2
)を比較する場合、1
と2
が比較されます。空の配列(例:
[ ]
)を比較する場合、空の配列はnull
値より小さいか、フィールド値が欠落していると見なされます。
インデックスの使用と照合
文字列の比較にインデックスを使用するには、操作で同じ照合も指定する必要があります。つまり、照合順序を持つインデックスでは、操作で異なる照合順序が指定されている場合、インデックス付きフィールドで文字列比較を実行する操作をサポートできません。
警告
照合が構成されたインデックスは、並べ替え順序を実現するために ICU 照合キーを使用するため、照合対応のインデックス キーは、照合のないインデックスのインデックス キーよりも大きくなる可能性があります。
たとえば、コレクション myColl
には、照合ロケール "fr"
を持つ文字列フィールド category
のインデックスがあります。
db.myColl.createIndex( { category: 1 }, { collation: { locale: "fr" } } )
インデックスと同じ照合を指定する次のクエリ操作では、インデックスを使用できます。
db.myColl.find( { category: "cafe" } ).collation( { locale: "fr" } )
ただし、デフォルトで「シンプル」なバイナリー コレータを使用する次のクエリ操作では、インデックスを使用できません。
db.myColl.find( { category: "cafe" } )
インデックス プレフィックスキーが文字列、配列、および埋め込みドキュメントではない複合インデックスの場合でも、異なる照合を指定する操作では、インデックスを使用してインデックス プレフィックスキーの比較をサポートできます。
たとえば、myColl
コレクションには、数値フィールドの score
、price
、および 文字列フィールドの category
の複合インデックスがあります。このインデックスは、文字列比較用の照合ロケール "fr"
を使用して作成されます。
db.myColl.createIndex( { score: 1, price: 1, category: 1 }, { collation: { locale: "fr" } } )
文字列の比較に "simple"
バイナリ照合を使用する次の操作では、インデックスを使用できます。
db.myColl.find( { score: 5 } ).sort( { price: 1 } ) db.myColl.find( { score: 5, price: { $gt: NumberDecimal( "10" ) } } ).sort( { price: 1 } )
次の操作では、"simple"
バイナリ照合を使用してインデックス付きの category
フィールドで文字列を比較しますが、クエリの score: 5
部分の実行についてはインデックスが使用できます。
db.myColl.find( { score: 5, category: "cafe" } )
重要
ドキュメント キーとの照合(埋め込みドキュメントのキーを含む)では、単純なバイナリ比較が使用されます。つまり、"foo.bár" のようなキーのクエリは、strength パラメーターに設定した値にかかわらず、キー "foo.bar" と一致しません。
例
次の例では、インデックス キーの型が同じ場合と異なる場合のソートを示しています。
keyTypes
コレクションを次のように作成します。
db.keyTypes.insertMany( [ { seqNum: 1, seqType: null, type: "null" }, { seqNum: 29, seqType: null, type: "null" }, { seqNum: 2, seqType: Int32("10"), type: "Int32" }, { seqNum: 28, seqType: Int32("10"), type: "Int32" }, { seqNum: 3, seqType: Long("10"), type: "Long" }, { seqNum: 27, seqType: Long("10"), type: "Long" }, { seqNum: 4, seqType: Decimal128("10"), type: "Decimal128" }, { seqNum: 26, seqType: Decimal128("10"), type: "Decimal128" }, { seqNum: 5, seqType: Double("10"), type: "Double" }, { seqNum: 25, seqType: Double("10"), type: "Double" }, { seqNum: 6, seqType: String("10"), type: "String" }, { seqNum: 24, seqType: String("10"), type: "String" }, { seqNum: 7, seqType: [ "1", "2", "3" ], type: "Array" }, { seqNum: 23, seqType: [ "1", "2", "3" ], type: "Array" }, { seqNum: 8, seqType: [ [1], [2], [3] ], type: "Array" }, { seqNum: 22, seqType: [ [1], [2], [3] ], type: "Array " }, { seqNum: 9, seqType: [ 1, 2, 3 ], type: "Array" }, { seqNum: 21, seqType: [ 1, 2, 3 ], type: "Array" }, { seqNum: 10, seqType: true, type: "Boolean" }, { seqNum: 11, seqType: new Timestamp(), type: "Timestamp" }, { seqNum: 12, seqType: new Date(), type: "Date" }, { seqNum: 13, seqType: new ObjectId(), type: "ObjectId" }, ] )
次のように、シーケンス番号(seqNum
)とシーケンス型(seqType
)フィールドにインデックスを作成します。
db.keyTypes.createIndex( { seqNum: 1 } ) db.keyTypes.createIndex( { seqType: 1 } )
find()
を使用してコレクションをクエリします。プロジェクション ドキュメント { _id: 0 }
は、出力表示で _id
フィールドを抑制します。
db.keyTypes.find( {}, { _id: 0 } )
ドキュメントは、次のように挿入順序で返されます。
{ seqNum: 1, seqType: null, type: 'null' }, { seqNum: 29, seqType: null, type: 'null' }, { seqNum: 2, seqType: 10, type: 'Int32' }, { seqNum: 28, seqType: 10, type: 'Int32' }, { seqNum: 3, seqType: Long("10"), type: 'Long' }, { seqNum: 27, seqType: Long("10"), type: 'Long' }, { seqNum: 4, seqType: Decimal128("10"), type: 'Decimal128' }, // Output truncated
シーケンス番号(seqNum
)インデックスには同じ型の値があります。keyTypes
コレクションをクエリするには、seqNum
インデックスを使用します。
db.keyTypes.find( {}, { _id: 0 } ).sort( { seqNum: 1} )
seqNum
キーは整数です。ドキュメントは、次のように番号順に返されます。
{ seqNum: 1, seqType: null, type: 'null' }, { seqNum: 2, seqType: 10, type: 'Int32' }, { seqNum: 3, seqType: Long("10"), type: 'Long' }, { seqNum: 4, seqType: Decimal128("10"), type: 'Decimal128' }, { seqNum: 5, seqType: 10, type: 'Double' }, { seqNum: 6, seqType: '10', type: 'String' }, { seqNum: 7, seqType: [ '1', '2', '3' ], type: 'Array' }, // Output truncated
シーケンス型( seqType
)インデックスには、さまざまな型の値があります。keyTypes
コレクションをクエリするには、seqType
インデックスを使用します。
db.keyTypes.find( {}, { _id: 0 } ).sort( { seqType: 1} )
ドキュメントは、次のように BSON 型のソート順序で返されます。
{ seqNum: 1, seqType: null, type: 'null' }, { seqNum: 29, seqType: null, type: 'null' }, { seqNum: 9, seqType: [ 1, 2, 3 ], type: 'Array' }, { seqNum: 21, seqType: [ 1, 2, 3 ], type: 'Array' }, { seqNum: 2, seqType: 10, type: 'Int32' }, { seqNum: 28, seqType: 10, type: 'Int32' }, { seqNum: 3, seqType: Long("10"), type: 'Long' }, { seqNum: 27, seqType: Long("10"), type: 'Long' }, { seqNum: 4, seqType: Decimal128("10"), type: 'Decimal128' }, { seqNum: 26, seqType: Decimal128("10"), type: 'Decimal128' }, { seqNum: 5, seqType: 10, type: 'Double' }, { seqNum: 25, seqType: 10, type: 'Double' }, { seqNum: 7, seqType: [ '1', '2', '3' ], type: 'Array' }, { seqNum: 23, seqType: [ '1', '2', '3' ], type: 'Array' }, { seqNum: 6, seqType: '10', type: 'String' }, { seqNum: 24, seqType: '10', type: 'String' }, { seqNum: 8, seqType: [ [ 1 ], [ 2 ], [ 3 ] ], type: 'Array' }, { seqNum: 22, seqType: [ [ 1 ], [ 2 ], [ 3 ] ], type: 'Array ' }, { seqNum: 13, seqType: ObjectId("6239e3922604d5a7478df071"), type: 'ObjectId' }, { seqNum: 10, seqType: true, type: 'Boolean' }, { seqNum: 12, seqType: ISODate("2022-03-22T14:56:18.100Z"), type: 'Date' }, { seqNum: 11, seqType: Timestamp({ t: 1647960978, i: 1 }), type: 'Timestamp' }
配列の比較では、次のようになります。
小なり比較または昇順ソートでは、BSON 型のソート順で配列の最小要素が比較されます。
大なり比較または降順ソートでは、BSON 型のソート順の逆順で配列の最大要素が比較されます。
値が 1 要素の配列(例:
[ 1 ]
)と配列以外のフィールド(例:2
)を比較する場合、1
と2
が比較されます。空の配列(例:
[ ]
)を比較する場合、空の配列はnull
値より小さいか、フィールド値が欠落していると見なされます。
数値型(Int32、Long、Decimal128、Double)は、他の型と比較すると同値です。
Numbers BSON 型内では、数値型は次のようにソートされます。
Int32
Long
Decimal128
Double