文档菜单
文档首页
/
MongoDB 手册
/ /

使用索引对查询结果进行排序

本页内容

  • 使用单字段索引进行排序
  • 在多个字段上进行排序
  • 索引排序顺序
  • 索引使用和排序规则
  • 示例

由于索引包含有序记录,MongoDB 可以从包含排序字段的索引中获取排序结果。如果排序使用与查询谓词相同的索引,MongoDB 可能 会使用多个索引来支持排序操作 如果

如果 MongoDB 无法使用索引或索引来获取排序顺序,MongoDB 必须在数据上执行阻塞排序操作。阻塞排序表示 MongoDB 必须在返回结果之前消费和处理所有输入文档。阻塞排序不会阻止对集合或数据库的并发操作。

从 MongoDB 6.0 开始,如果服务器要求执行管道执行阶段超过 100 兆字节的内存,MongoDB 会自动将临时文件写入磁盘,除非该查询指定{ allowDiskUse: false }。如果服务器需要超过 100 兆字节的系统内存来执行阻塞排序操作,MongoDB 会返回错误,除非该查询指定cursor.allowDiskUse(). 详细信息请参阅 allowDiskUseByDefault.

使用索引的排序操作通常比阻塞排序具有更好的性能。

注意

当你根据一个以多键索引索引的数组字段进行排序时,查询计划将包含一个阻塞排序阶段,除非以下两个条件都成立:

  • 所有排序字段的索引边界都是[MinKey, MaxKey]

  • 没有任何多键索引字段的边界与排序模式具有相同的路径前缀。

如果一个升序或降序索引在一个单个字段上,该字段的排序操作可以是任一方向。

例如,为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 可以使用索引对查询结果进行排序。复合索引的前缀是由索引键模式开始的一个或多个键的子集。

例如,在 data 集合上创建一个复合索引

db.data.createIndex( { a:1, b: 1, c: 1, d: 1 } )

然后,以下是该索引的前缀

{ a: 1 }
{ a: 1, b: 1 }
{ a: 1, b: 1, c: 1 }

以下查询和排序操作使用索引前缀对结果进行排序。这些操作不需要在内存中排序结果集。

示例
索引前缀
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 }

以下操作可以使用索引来获取排序顺序

示例
索引前缀
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 },但查询文档没有包含对前缀索引字段 ab 的强等于匹配

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类型排序顺序比较数组中的最大元素。

    • 比较查询操作符,例如 $lt$gt,按字典顺序对数组进行比较。

    • 当比较一个值为单元素数组(例如,[ 1 ])的域与非数组字段(例如,2)时,比较的是 12

    • 空数组(例如,[ ])的比较将空数组视为小于一个 null 值或缺失的字段值。

    • 嵌套数组的比较(例如,[[1, 2], [3, 4]])将最外层数组之后的任何数组按字典顺序比较。

请参阅索引排序示例.

要使用索引进行字符串比较,操作也必须指定相同的排序。也就是说,具有排序的索引不支持在索引字段上进行字符串比较的操作,如果该操作指定了不同的排序。

警告

由于配置了排序的索引使用ICU排序键来达到排序顺序,具有排序意识的索引键可能比未配置排序的索引键更大。

例如,集合myColl在字符串字段category上有一个索引,排序区域设置为"fr"

db.myColl.createIndex( { category: 1 }, { collation: { locale: "fr" } } )

以下查询操作,指定了与索引相同的排序,可以使用该索引

db.myColl.find( { category: "cafe" } ).collation( { locale: "fr" } )

然而,以下查询操作,默认使用“简单”二进制排序,不能使用索引

db.myColl.find( { category: "cafe" } )

对于复合索引,其中索引前缀键不是字符串、数组和嵌入文档,指定不同排序的操作仍然可以使用索引来支持索引前缀键的比较。

例如,集合myColl在数值字段scoreprice以及字符串字段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”之类的键的查询不会与键“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)索引具有相同类型的值。使用seqNum索引查询keyTypes集合

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)索引具有不同类型的值。使用seqType索引查询keyTypes集合

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类型排序顺序比较数组中的最大元素。

    • 比较查询操作符,例如 $lt$gt,按字典顺序对数组进行比较。

    • 当比较一个值为单元素数组(例如,[ 1 ])的域与非数组字段(例如,2)时,比较的是 12

    • 空数组(例如,[ ])的比较将空数组视为小于一个 null 值或缺失的字段值。

    • 嵌套数组的比较(例如,[[1, 2], [3, 4]])将最外层数组之后的任何数组按字典顺序比较。

  • 数值类型(Int32、Long、Decimal128、Double)与其他类型比较时相等。

  • 在数值BSON类型内部,数值类型按顺序排序

    • Int32

    • Long

    • Decimal128

    • Double

返回

等价性、排序、范围规则