本文主要讨论 “mongodb的索引” 和 “查询结果的排序” 之间的关系,索引对排序有什么影响,为什么有影响,应该遵循什么规则。
db.集合名.createIndex(要给哪一列创建索引 [,额外选项]);
第1个参数是 “给哪一列创建索引”,它的格式是: {key:1或-1},其中1表示升序,-1表示降序。
第2个参数是 “额外选项”,它是可选的,比如:设置索引名称、指定索引类型等等。
举例: db.集合名.createIndex({age:1}) ; —> 表示在age字段上创建索引并按照升序的方式存储索引数据。
注意:索引也是一堆数据,索引是一些按照指定规则排序的数据, 最终也是被存储起来的, 也是占用磁盘空间的。
所以,在创建索引的时候要指定一个存储顺序(1升序, -1降序) ,告诉它是升序存储还是降序存储。
在MongoDB中,排序操作,可以通过从索引中按照索引的顺序获取文档的方式,来保证结果的有序性。
如果MongoDB的查询计划器(planner) 没法从索引中得到排序顺序,那么它就需要在内存中对结果排序。
相比于“不用索引的排序”操作,用索引会有更好的性能。
注意,关键是:不用索引的排序操作,会在用了超过32MB内存时终止,也就是说MongoDB只能支持32MB的非索引排序 。
如果数据量很大,比如我目前的应用场景下,都是千万级数据量,而且飞速增加,达亿级……
索引,是以升序(1) 或 降序(-1) 的排序顺序,存储对字段的引用。
单字段索引的排序
如果在单字段上是升序索引或降序索引,则对该字段的排序操作可以是任一方向。
例如:在集合records的a字段上创建一个升序索引:db.records.createIndex( { a: 1 } ) ;
上面的索引可以支持在 a 字段上的升序排序:db.records.find().sort( { a: 1 } ) ;
上面是索引还可以通过按相反的顺序遍历索引,来支持在 a 字段上的降序排序:db.records.find().sort( { a: -1 } ) ;
对于单字段索引,字段的排序顺序并不重要,因为MongoDB可以在任意方向遍历索引。
但是,对于复合索引,排序顺序在确定索引是否支持排序操作时很重要。
复合索引的排序
你可以指定在索引的所有字段或者部分字段上排序。
用复合索引排序时,sort() 中所指定的排序字段有以下两点需要遵循,否则不会走索引排序:
(1)排序字段的 “排列顺序”,必须和它们在索引中的排列顺序一致。
划重点:前后顺序,必须一致。
例如,索引 { a: 1, b: 1 } ,可以支持 { a: 1, b: 1 } 上的排序,但不支持 { b: 1, a: 1 } 上的排序。
(2)排序字段的 “排序顺序”,必须和索引中的对应字段的排序顺序完全相同或完全相反。
划重点:升降序顺序,要么完全一样、要么完全相反。
例如,索引 { a: 1, b: -1 } ,可以支持 { a: 1, b: -1 } 和 { a: -1, b: 1 } 的排序,但不支持 { a: -1, b: -1 } 或 { a: 1, b: 1 } 的排序。
实例:events集合中包含username和date字段,建立复合索引 db.events.createIndex( { "username" : 1, "date" : -1 } ) ,它可以支持下面两种排序操作:
db.events.find().sort( { username: 1, date: -1 } ) 先按username升序再按date降序,它和索引字段的升降序顺序完全一样。
db.events.find().sort( { username: -1, date: 1 } ) 先按username降序再按date升序,它和索引字段的升降序顺序完全相反。
上面的索引不支持 db.events.find().sort( { username: 1, date: 1 } ) 先按username升序再按date升序的排序,因为它和索引字段的升降序顺序 既不完全一样、也不完全相反。
如果“排序字段”能和“索引字段或索引前缀”对应起来,那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 } |
索引也支持使用非前缀字段来排序。
不过这种情况是有前提的,就是:查询语句中 必须 把排序字段之前的所有前缀字段,都加上相等条件。
例如,集合 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 } ,但是查询语句并没有对前缀字段 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 } ,甚至可能不用索引检索文档。