【使用explain和hint】
前面讲高级查询选项时,提到过"$explain" 和 ”$hint“可以作为包装查询的选项关键字使用,其实这两个本身就可以作为操作游标的函数调用!游标调用explain函数会返回一个文档,用于描述当前查询的一些细节信息。这也不同于我们前面介绍的游标函数,前面提到的游标处理函数都是返回游标,可组成方法链调用。我们看一下explain的具体应用
- > db.blogs.findOne();
- {
- "_id" : ObjectId("502262ab09248743250688ea"),
- "content" : ".....",
- "comment" : [
- {
- "author" : "joe",
- "score" : 3,
- "comment" : "just so so!"
- },
- {
- "author" : "jimmy",
- "score" : 5,
- "comment" : "cool! good!"
- }
- ]
- }
- > db.blogs.find({"comment.author":"joe"}).explain();
- {
- "cursor" : "BtreeCursor comment.author_1",
- "nscanned" : 1,
- "nscannedObjects" : 1,
- "n" : 1,
- "millis" : 70,
- "nYields" : 0,
- "nChunkSkips" : 0,
- "isMultiKey" : true,
- "indexOnly" : false,
- "indexBounds" : {
- "comment.author" : [
- [
- "joe",
- "joe"
- ]
- ]
- }
- }
我们在集合blogs上为内嵌文档的键“comment.author”建立了索引,然后我们使用这个键作为查询条件查询文档,在游标上调用explain返回上述文档,我们解释一下返回文档几个主要键的含义:
1》 “cursor”:因为这个查询使用了索引,MongoDB中索引存储在B树结构中,所以这是也使用了BtreeCursor类型的游标。如果没有使用索引,游标的类型是BasicCursor。这个键还会给出你所使用的索引的名称,你通过这个名称可以查看当前数据库下的system.indexes集合(系统自动创建,由于存储索引信息,这个稍微会提到)来得到索引的详细信息。
2》 “nscanned”/“nscannedObjects”:表明当前这次查询一共扫描了集合中多少个文档,我们的目的是,让这个数值和返回文档的数量越接近越好。
3》 "n":当前查询返回的文档数量。
4》 “millis”:当前查询所需时间,毫秒数。
5》 “indexBounds”:当前查询具体使用的索引
上述我们的文档只是定义了一个内嵌文档的索引,我们查询也正好使用了这个键,这个情况比较简单。我们再看一个稍微复杂些的情况!集合user上有两个索引{"name":1,“age”:1}和{“age”:1,“name”:1},我们按照这两个键查询,再看看explain的返回:
- > db.user.ensureIndex({"name":1,"age":1});
- > db.user.ensureIndex({"age":1,"name":1});
- > db.user.find({"age":40, "name":"tim"}).explain();
- {
- "cursor" : "BtreeCursor name_1_age_1",
- "nscanned" : 1,
- "nscannedObjects" : 1,
- "n" : 1,
- "millis" : 0,
- "nYields" : 0,
- "nChunkSkips" : 0,
- "isMultiKey" : false,
- "indexOnly" : false,
- "indexBounds" : {
- "name" : [
- [
- "tim",
- "tim"
- ]
- ],
- "age" : [
- [
- 40,
- 40
- ]
- ]
- }
- }
- >
我们看,返回文档的键没有区别,其默认使用了索引"name_1_age_1",这是查询优化器为我们使用的索引!我们此处可以通过hint进行更行,即强制这个查询使用我们定义的“age_1_name_1”索引,如下:
- > var cursor = db.user.find({"age":40, "name":"tim"}).hint({"age":1,"name":1});
- > cursor.explain();
- {
- "cursor" : "BtreeCursor age_1_name_1",
- "nscanned" : 1,
- "nscannedObjects" : 1,
- "n" : 1,
- "millis" : 0,
- "nYields" : 0,
- "nChunkSkips" : 0,
- "isMultiKey" : false,
- "indexOnly" : false,
- "indexBounds" : {
- "age" : [
- [
- 40,
- 40
- ]
- ],
- "name" : [
- [
- "tim",
- "tim"
- ]
- ]
- }
- }
- >
我们看,hint函数不同于explain函数,会返回游标,我们可以在游标上调用explain查看索引的使用情况!99%的情况,我们没有必要通过hint去强制使用某个索引,MongoDB的查询优化器非常智能,绝对能帮助我们使用最佳的索引去进行查询!
【索引管理】
上面提到索引的元信息(描述信息)存储在集合system.indexes中,这是系统提供的保留集合(创建数据库时),我们不能对其进行插入或删除操作!我们可以从中查看索引定义的相关信息,我们操作这个集合只能通过ensureIndex(插入索引),dropIndex(删除索引)两个函数!我们先看看system.indexes这个集合吧:
- > db.system.indexes.find();
- { "v" : 1, "key" : { "_id" : 1 }, "ns" : "mylearndb.blogs", "name" : "_id_" }
- { "v" : 1, "key" : { "_id" : 1 }, "ns" : "mylearndb.people", "name" : "_id_" }
- { "v" : 1, "key" : { "_id" : 1 }, "ns" : "mylearndb.users", "name" : "_id_" }
系统会默认为集合创建"_id"唯一性索引,所以这个表会有很多这种“name”为“_id_”的索引信息。键“ns”是“数据库名.集合名”,我们可以通过这个查询一个集合上定义的所有索引:
- > db.system.indexes.find({"ns":"mylearndb.user"});
- { "v" : 1, "key" : { "_id" : 1 }, "ns" : "mylearndb.user", "name" : "_id_" }
- { "v" : 1, "key" : { "name" : 1 }, "unique" : true, "ns" : "mylearndb.user", "name" : "name_1", "dropDups" : true }
- { "v" : 1, "key" : { "name" : 1, "age" : 1 }, "ns" : "mylearndb.user", "name" : "name_1_age_1" }
- { "v" : 1, "key" : { "age" : 1, "name" : 1 }, "ns" : "mylearndb.user", "name" : "age_1_name_1" }
- >
我们也可通过键"name”来查询特定索引的元信息,这里不做演示了。
【修改索引】
随着应用数据的积累或集合结构的改变,老的索引会出现效率低下的问题,修改索引页也是不可避免了。我们可以随时通过ensureIndex函数为集合添加索引,这个函数前面已经多次使用,这里我们再介绍该函数第二个参数文档的一个键"background",布尔类型,表明是否在数据库服务空闲时来构建索引,因为索引的构建是一个耗时耗资源的过程,并且在构建过程中,数据库会阻塞所有的访问请求,对于一个大数量的集合添加索引我们应该启用这个选型!我们还要知道的是,即时启用了这个选项,构建仍会影响正常服务,但不会彻底阻塞数据库服务:
- > db.user.ensureIndex({"name":1,"registered":-1},{"background":true});
- >
其使用方式和以前创建索引的过程没有区别。
对于没有用的索引,我们应尽快删除,因为索引会影响数据库的增删改的效率!利用集合的dropIndex(indexName)删除一个集合上的特定索引。我们使用这个函数的正确步骤应该是先通过查询system.indexes确认索引的名称,然后再删除,这样做是因为不是所有语言的数据库驱动都是按照我们前面介绍的方式去生成索引名称:
- > db.system.indexes.find({"ns":"mylearndb.user"});
- { "v" : 1, "key" : { "_id" : 1 }, "ns" : "mylearndb.user", "name" : "_id_" }
- { "v" : 1, "key" : { "name" : 1, "age" : 1 }, "ns" : "mylearndb.user", "name" :
- { "v" : 1, "key" : { "age" : 1, "name" : 1 }, "ns" : "mylearndb.user", "name" :
- > db.user.dropIndex("name_1_age_1");
- { "nIndexesWas" : 3, "ok" : 1 }
- > db.system.indexes.find({"ns":"mylearndb.user"});
- { "v" : 1, "key" : { "_id" : 1 }, "ns" : "mylearndb.user", "name" : "_id_" }
- { "v" : 1, "key" : { "age" : 1, "name" : 1 }, "ns" : "mylearndb.user", "name" :
- >
我们需要注意,集合还有一个函数dropIndexes,不接受任何参数,这个函数要慎用啊,他会直接将集合所有的索引全部删掉!Shell中还可以通过运行命令的方式删除一个索引:
- > db.system.indexes.find({"ns":"mylearndb.user"});
- { "v" : 1, "key" : { "_id" : 1 }, "ns" : "mylearndb.user", "name" : "_id_" }
- { "v" : 1, "key" : { "age" : 1, "name" : 1 }, "ns" : "mylearndb.user", "name" : "age_1_name_1" }
- > db.runCommand({"dropIndexes":"user","index":"age_1_name_1"});
- { "nIndexesWas" : 2, "ok" : 1 }
- > db.system.indexes.find({"ns":"mylearndb.user"});
- { "v" : 1, "key" : { "_id" : 1 }, "ns" : "mylearndb.user", "name" : "_id_" }
- >
从上述的例子可看到这个命令的使用情况,其中键"dropIndexes"指明要删除索引所在的集合,键"index"指明要删除的索引的名称!如果这里名称指明为一个“*”,则表明是删除集合上所有的索引!
除此之外,还有一种删除索引的方式是将集合删掉,这样所有索引(包括键“_id”的唯一索引)、文档都会被删除。上述的删除所有索引的方式都不会删除系统为键“_id”创建的唯一索引。调用集合的remove函数,即使删除所有文档,也不会删除索引,当你往集合中添加数据时,该索引还会起作用。
MongoDB中,往含有数据的集合上添加索引比向空集合添加索引后插入数据要快一些,这个在进行数据库数据初始化时可以考虑一下。
【地理空间索引】
目前网络上LBS(location based service)越来越流行,有一个应用就是查询你所在位置附件的某些场所。为了提升这种查询的速度(查询不同于上面单维度,需要搜索两个维度),MongoDB为坐标平面查询提供了专门的索引,即称作地理空间索引。
由于建立地理空间索引的键的值必须是一对值:一个包含两个数值的数组或包含两个键的内嵌文档(内嵌文档的键的名称无所谓),如:{“gps”:[123,134]},{“gps”:{“x”:123,“y”:134}},{“gps”:{“latitude”:123,“longitude”:134}}。这些文档的键“gps”,我们都可以再上面建立地理空间索引:
- > db.shopstreet.find();
- { "_id" : ObjectId("502673678a84caa12e8070be"), "desc" : "coffeehouse", "gps" : [ 100, 120 ] }
- { "_id" : ObjectId("502673738a84caa12e8070bf"), "desc" : "coffeebar", "gps" : [ 110, 130 ] }
- { "_id" : ObjectId("502673838a84caa12e8070c0"), "desc" : "coffee king", "gps" : [ 110, 123 ] }
- { "_id" : ObjectId("502673a08a84caa12e8070c1"), "desc" : "coffee buck", "gps" : [ 106, 113 ] }
- { "_id" : ObjectId("502674028a84caa12e8070c2"), "desc" : "nike shop", "gps" : [ 109, 111 ] }
- > db.shopstreet.ensureIndex({"gps" : "2d"});
- >
建立地理空间索引同样调用ensureIndex方法,{"gps" : "2d"},以前建立索引键的值为1,或-1,地理空间索引的值固定为"2d"。地理空间索引默认值的范围为(-180~180)(对于经纬度很适合),但我们在创建索引时可以指定其值的范围:
- > db.shopstreet.ensureIndex({"gps" : "2d"},{"min":-1000,"max":1000});
- >
上面我们创建的地理空间索引值的范围为-1000~1000。
地理空间查询可以通过find或使用数据库命令。这里我们需要使用“$near”查询操作符,如下:
- > db.shopstreet.find({"gps":{"$near" : [110,130]}});
- { "_id" : ObjectId("502673738a84caa12e8070bf"), "desc" : "coffeebar", "gps" : [ 110, 130 ] }
- { "_id" : ObjectId("502673838a84caa12e8070c0"), "desc" : "coffee king", "gps" : [ 110, 123 ] }
- { "_id" : ObjectId("502673678a84caa12e8070be"), "desc" : "coffeehouse", "gps" : [ 100, 120 ] }
- { "_id" : ObjectId("502673a08a84caa12e8070c1"), "desc" : "coffee buck", "gps" : [ 106, 113 ] }
- { "_id" : ObjectId("502674028a84caa12e8070c2"), "desc" : "nike shop", "gps" : [ 109, 111 ] }
- >
这个查询会按点(110,130)来查询文档,由远及近将符合条件的文档返回,如果没有在游标上使用limit函数,默认会返回100条文档。通常我们会利用limit限制前几个最靠近的目标文档即可!我们可以使用数据库命令完成上述查询:
- > db.shopstreet.ensureIndex({"gps" : "2d"},{"min":-1000,"max":1000});
- > db.shopstreet.find({"gps":{"$near" : [110,130]}});
- { "_id" : ObjectId("502673738a84caa12e8070bf"), "desc" : "coffeebar", "gps" : [ 110, 130 ] }
- { "_id" : ObjectId("502673838a84caa12e8070c0"), "desc" : "coffee king", "gps" : [ 110, 123 ] }
- { "_id" : ObjectId("502673678a84caa12e8070be"), "desc" : "coffeehouse", "gps" : [ 100, 120 ] }
- { "_id" : ObjectId("502673a08a84caa12e8070c1"), "desc" : "coffee buck", "gps" : [ 106, 113 ] }
- { "_id" : ObjectId("502674028a84caa12e8070c2"), "desc" : "nike shop", "gps" : [ 109, 111 ] }
- > db.runCommand({"geoNear":"shopstreet", "near":[110,130], "num":3});
- {
- "ns" : "mylearndb.shopstreet",
- "near" : "1111000111111000000111111000000111111000000111111000",
- "results" : [
- {
- "dis" : 0,
- "obj" : {
- "_id" : ObjectId("502673738a84caa12e8070bf"),
- "desc" : "coffeebar",
- "gps" : [
- 110,
- 130
- ]
- }
- },
- {
- "dis" : 7,
- "obj" : {
- "_id" : ObjectId("502673838a84caa12e8070c0"),
- "desc" : "coffee king",
- "gps" : [
- 110,
- 123
- ]
- }
- },
- {
- "dis" : 14.142135623730951,
- "obj" : {
- "_id" : ObjectId("502673678a84caa12e8070be"),
- "desc" : "coffeehouse",
- "gps" : [
- 100,
- 120
- ]
- }
- }
- ],
- "stats" : {
- "time" : 0,
- "btreelocs" : 0,
- "nscanned" : 5,
- "objectsLoaded" : 4,
- "avgDistance" : 7.04737854124365,
- "maxDistance" : 14.142152482638018
- },
- "ok" : 1
- }
- >
这个命令接受一个文档,文档中键"geoNear"指明查询的集合,键"near"指明查询的基准坐标值,键"num"指定返回的结果数量!然后执行后返回如上结果,这个命令同时还会返回每个返回文档距查询点的距离,这个距离的数据单位就是你数据的单位,如上述数据位经纬度,键“dis”后面的距离数值就是经纬度!
MongoDB不但能找到靠近一个点的文档,还能找到指定形状内的文档!使用查询操作符"$within"即可,同时通过MongoDB提供的查询操作符指定形状,如“$box”可以指定矩形,“$center”可以指定圆形:
- > db.shopstreet.find();
- { "_id" : ObjectId("502673678a84caa12e8070be"), "desc" : "coffeehouse", "gps" : [ 100, 120 ] }
- { "_id" : ObjectId("502673738a84caa12e8070bf"), "desc" : "coffeebar", "gps" : [ 110, 130 ] }
- { "_id" : ObjectId("502673838a84caa12e8070c0"), "desc" : "coffee king", "gps" : [ 110, 123 ] }
- { "_id" : ObjectId("502673a08a84caa12e8070c1"), "desc" : "coffee buck", "gps" : [ 106, 113 ] }
- { "_id" : ObjectId("502674028a84caa12e8070c2"), "desc" : "nike shop", "gps" : [ 109, 111 ] }
- > db.shopstreet.find({"gps" : {"$within" : {"$box":[[109,130],[110,120]]}}});
- { "_id" : ObjectId("502673838a84caa12e8070c0"), "desc" : "coffee king", "gps" : [ 110, 123 ] }
- { "_id" : ObjectId("502673738a84caa12e8070bf"), "desc" : "coffeebar", "gps" : [ 110, 130 ] }
通过{"$box":[[109,130],[110,120]]}指定一个矩形,其左下角和右上角坐标!“$within”指定查询在这个范围内的点。
- > db.shopstreet.find();
- { "_id" : ObjectId("502673678a84caa12e8070be"), "desc" : "coffeehouse", "gps" : [ 100, 120 ] }
- { "_id" : ObjectId("502673738a84caa12e8070bf"), "desc" : "coffeebar", "gps" : [ 110, 130 ] }
- { "_id" : ObjectId("502673838a84caa12e8070c0"), "desc" : "coffee king", "gps" : [ 110, 123 ] }
- { "_id" : ObjectId("502673a08a84caa12e8070c1"), "desc" : "coffee buck", "gps" : [ 106, 113 ] }
- { "_id" : ObjectId("502674028a84caa12e8070c2"), "desc" : "nike shop", "gps" : [ 109, 111 ] }
- > db.shopstreet.find({"gps" : {"$within" : {"$center":[[110,130],10]}}});
- { "_id" : ObjectId("502673838a84caa12e8070c0"), "desc" : "coffee king", "gps" : [ 110, 123 ] }
- { "_id" : ObjectId("502673738a84caa12e8070bf"), "desc" : "coffeebar", "gps" : [ 110, 130 ] }
- >
上述是通过指定一个圆形来查询同样的点,{"$center":[[110,130],10]},指定了圆形的圆心坐标和半径!
【复合地理空间索引】
通常我们查找一个位置,不会只是通过坐标去定位,还会添加其他条件,我们构建索引时也可以用上:
- > db.shopstreet.find();
- { "_id" : ObjectId("502673678a84caa12e8070be"), "desc" : "coffeehouse", "gps" : [ 100, 120 ] }
- { "_id" : ObjectId("502673738a84caa12e8070bf"), "desc" : "coffeebar", "gps" : [ 110, 130 ] }
- { "_id" : ObjectId("502673838a84caa12e8070c0"), "desc" : "coffee king", "gps" : [ 110, 123 ] }
- { "_id" : ObjectId("502673a08a84caa12e8070c1"), "desc" : "coffee buck", "gps" : [ 106, 113 ] }
- { "_id" : ObjectId("502674028a84caa12e8070c2"), "desc" : "nike shop", "gps" : [ 109, 111 ] }
- > db.showstreet.ensureIndex({"gps":"2d", "desc":1});
- >
上述就创建了一个复合地理空间索引,这个索引更符合实际需要!
上述就是MongoDB中索引的使用,索引是数据库查询提升效率的利器,对于任何数据库都是如此!我们应该好好掌握!
mongodb索引讲解与性能调优
mongodb索引规则基本上与传统的关系库一样,大部分优化MySQL/Oracle/SQLite索引的技巧也适用于mongodb。
当查询中用到某些条件时,可以对该键建立索引,以提高查询速度。
查询索引很简单,比如说需要查询mailaccess数据库中的Mail collection上的索引时:
mongo 进入mongo
MongoDB shell version: 1.8.1
connecting to: test
> use mailaccess 进入mailaccess database
switched to db mailaccess
> db.Mail.getIndexes() 查询索引明细
[
{
"name" : "_id_",
"ns" : "mailaccess.Mail",
"key" : {
"_id" : 1
},
"v" : 0
},
{
"_id" : ObjectId("4df063ac48857df7ac35c348"),
"ns" : "mailaccess.Mail",
"key" : {
"user" : 1,
"folderId" : 1,
"mailfilename" : 1
},
"name" : "user_1_folderId_1_mailfilename_1",
"v" : 0
},
……
> db.Mail.totalIndexSize() 查询索引大小
114688 索引所占大小,单位:字节
db.Mail.ensureIndex({user:1,folderId:1,mailfilename:1})
该命令会在Mail上创建一索引,默认索引名称的规则为:
Keyname1_dir1_keyname2_dir2…,如果该索引的默认名称:
user_1_folderId_1_mailfilename_1
其中:1表示升序 -1表示降序
要是索引键很多时,最好自定义名称,如:定义名称为:index1
db.Mail.ensureIndex({user:1,folderId:1,mailfilename:1} ,{name:'index1'})
如需删除名称为index1的索引:
db.Mail.dropIndex(‘index’)
如需删除Mail中的所有索引时:
db.Mail.dropIndex(‘*’) _id索引不会删除
还有一种方式是删除collection,collection中的所以索引也会消失,注意:_id索引也会被删除。(删除collection中的数据不会删除索引)
Mongodb没有单独的修改索引的方法,如果需要修改某个索引,需要先删除旧有的索引,再创建新的索引。
随着数据量的不断增长,你可能会发现某个collection需要修改索引或增加索引,此时创建索引就会很费力了,同时也很消耗性能。创建索引时mongodb默认是阻塞式,阻塞会让索引建立得更快,任何此期间的请求将不能响应。可以使用{”background”:true}选项在后台完成,同时也可能正常处理请求。不过这种方式也会造成请求的响应很慢。如果非紧急情况,最好在晚上统一处理。
Mongodb每个collection都会有一个默认主键_id,这个不能删除、也不会更名。当collection创建后,系统会自动创建一个”_id_”的索引,这个也是无法删除与更名的。
在单个栏位上创建的索引,比如,需要对Mail的read创建升序索引:
db.Mail.ensureIndex({‘read’:1})
对多个键创建的索引,比如,需要对Mail的user与folderId创建降序索引:
db.Mail.ensureIndex({‘user’:-1,’folderId’:-1})
可以为内嵌文档的键创建索引,这种与普通索引没有什么区别,比如:需要对Mail中的attachments下的filename创建索引:
db.Mail.ensureIndex({‘attachments.filename’:1})
注意:attachments.filename必须位于’’之中,否则会报错
唯一索引可能确保collection的每一个document指定的键的唯一性。当文档不存在指定键时,会被认为键值是“null”,所以“null”也会被认为是重复的,所以一般被作为唯一索引的键,最好都要有键值对。比如:要保证Mail中每个用户的mailfilename的唯一性:
db.Mail.ensureIndex({‘user’:1,‘filename’:1},{name:’index1’,‘unique’:true})
当为已有的collection增加唯一索引时,可能会有数据已经重复了。有时候可能希望将所有包含重复的文档都删除,可能在创建唯一索引时,使用dropDups选项:
db.Mail.ensureIndex({‘user’:1,‘filename’:1},{‘unique’:true,’dropDups’:true})
这个会将重复的数据只保留一份,不过有点鲁莽,如果数据很重要的话,建议不好这样做。
注意了:
Insert并不检查文档是否插入过,所以确保数据的唯一性,可能要用安全模式插入才行。这样,在插入时,如果有重复就会有错误提醒
Sparse index解决索引文件过大的问题,有时候我们要索引的某个属性并非是所有记录都有,普通的索引是将所有的记录都包含进来,而sparse索引则仅包含含有这个属性的记录,它不会对该项值为空的行作索引。这样就大大减小了某些列的索引大小。目前的限制是,sparse index只能包含一个属性。比如:在Mail中有个标签属性labels,这个属性是唯一的,且有值的情况也不多,这种情况就最适合用sparse索引了,创建索引的命令为:
db.Mail.ensureIndex({labels:1},{sparse:true})
如果你查找的值正好是在索引中,则可以直接返回索引中存的值,而不用到数据文件中查找。(这个在传统关系型数据库中也有实现),不过,必须满足以下条件:
Ø 必须提供准备的返回字段,以便可以直接从索引库中查询
Ø 必须明确地排除使用_id字段{_id:0}
当用explain时,当indexOnly=true,表示有用到covered index:
// do a login with a covered index, returning the users roles/groups
> db.users.ensureIndex( { username : 1, password : 1, roles : 1} );
> db.users.save({username: "joe", password: "pass", roles: 2})
> db.users.save({username: "liz", password: "pass2", roles: 4})
> db.users.find({username: "joe"}, {_id: 0, roles: 1})
{ "roles" : 2 }
> db.users.find({username: "joe"}, {_id: 0, roles: 1}).explain()
{
"cursor" : "BtreeCursor username_1_password_1_roles_1",
...
"indexOnly" : true,
...
}
正则表达式可以灵活地匹配查询条件,如果希望正则表达式能命中索引,就要注意了:
Mongodb能为前缀型的正则表达式命中索引,比如:需要查询Mail中user以z开头的:
/^z/
如果有user索引,这种查询很高效
但其他的即使有索引,也不会命中索引,比说:需要查询Mail中的user中含有z的:
/.*z.*/
/^.*z.*/
这种查询是不会命中到索引的,当数据量很大,速度很慢
总之,^后的条件必须明确,不能^.* ^[a-z]之类开头的
假设索引为:{a:1,b:1,c:1,…,z:1}:
实际上是有了:{a:1},{a:1,b:1},{a:1,b:1,c:1}…等索引的。
但是使用{b:1}、{a:1,c:1}等索引的查询是会被优化的,只有使用索引前部的查询才能使用该索引。
Mongodb的查询优化器会重排查询项的顺序,以便命中索引,比如:查询{x:’a’,y:’b’}的时候,如果已有了{y:1,x:1}的索引,mongodb也会自己找到并命中的。
创建索引的缺点是每次插入、更新与删除时都会产生额外的开销,这是因为数据库不但需要执行这些操作,还是处理索引,因些,要尽量可能少创建索引。每个集合默认的最大索引个数为64个。
db.collection.find(query).explain();
返回的信息如下
{"cursor" : "BasicCursor",
"indexBounds" : [ ],
"nscanned" : 57594,
"nscannedObjects" : 57594,
"nYields" : 2 ,
"n" : 3 ,
"millis" : 108,
"indexOnly" : false}
现实结果可以得知cursor的类型,DB扫描的数据数,返回的数据数,还有执行的毫秒数。
"cursor" : "BasicCursor":
命中的索引,当为BasicCursor时表示没有命中任何索引
indexBounds: 所使用的索引,被设置为表示为索引扫描的关键边界。
nscanned - 扫描的数据条数。
nscannedObjects - 扫描对象的数。
nYields - 查询所产生的锁的个数。
isMultiKey- MongoDB中提供了可以自动索引数组对象的值
If true, a multikey index was used.
n- 返回文档的数量
millis- 数据库中执行查询的时间
indexOnly - 是否使用了covered index。
如果发现mongodb用了非预期的索引,可以用hint强制用某个索引,如:
db.Mail.find({user:'zhaoxy1@szdep.com',folderId:'inbox'}).hint(‘index1’)
多数情况下这种指定没有什么必要,mongodb会替你选择用哪个索引,初次查询时,查询优化器会同时尝试各种查询方案,最先完成的被确定使用,其他的则终止掉。查询方案也会记录下来,以备是后应对相同键的查询,查询优化器定期也重试其他的方案,以防因为添加新的数据后,之前的方案不再是最优的。
随着集合的增长,如果查询中有用到排序时,就要创建索引了。如果对没有索引的键用sort,mongodb需要将所有的数据提到内存中进行排序,这个是很影响性能的。
以MA的获取邮件列表为例,索引为:
{user:1,folderId:1,sendTime:-1,read:1,sourceSystem:1,importantFlag:1},{name:'folder_list_index'}
需要用到的查询条件组合(都需要以sendTime降序排列):
user:1,folderId:1,read:1,sourceSystem:1,importantFlag:1
user:1,folderId:1, read:1,sourceSystem:1
user:1,folderId:1,,read:1
user,folderId, sourceSystem:1,importantFlag:1
user,folderId, sourceSystem:1
注意:
红包部分必须hint,否则第一次速度会很快
用到排序时,sendTime必须还放在某个固定条件之前,比如:
大的范围为:user/folderId,sendTime则需放在此后,这样user/folderId对应的记录已经都以sendTime:-1的形式存放在索引库中(此时,用sort与不用sort的效果是相同的),即使后面再加其他的条件,sort时,也不用在内存中进行排序,这样与hint结合使用,每次查询都会很快。