MongoDB索引的使用
1 基本索引
在数据库开发中索引是非常重要的,对于检索速度,执行效率有很大的影响。本 文主要描述了MongoDB中索引的使用,以及通过分析执行计划来提高数据库检索 效率。
作为事例,在数据库中插入百万条数据,用于分析
> for (i = 0; i < 1000000; i++) {
"i" : i,
"username" : "user" + i,
"age" : Math.floor(Math.random() * 120),
"created" : new Date()
}
在MongoDB中,所有查询操作,都可以通过执行explain()函数来实现执行的分析, 通过执行查询username为user99999的用户,并执行查询分析,可以得出如下结 果:
> db.users.find({"username": "user99999"}).explain()
{
"cursor" : "BasicCursor",
"isMultiKey" : false,
"n" : 1,
"nscannedObjects" : 1000000,
"nscanned" : 1000000,
"nscannedObjectsAllPlans" : 1000000,
"nscannedAllPlans" : 1000000,
"scanAndOrder" : false,
"indexOnly" : false,
"nYields" : 1,
"nChunkSkips" : 0,
"millis" : 561,
"indexBounds" : { },
"server" : "WallE.local:27017"
}
其中,“n”表示查找到数据的个数,“nscanedObjects”表示本次查询需要扫描的 对象个数,“milis”表示此次查询耗费的时间,可以看到,这次查询相当于对整 个数据表进行了遍历,共一百万条数据,找到其中一条数据,耗费时间为561毫 秒。
我们也可以使用limit来限制查找的个数,从而提升效率,例如:
> db.users.find({"username": "user99999"}).limit(1).explain()
{
"cursor" : "BasicCursor",
"isMultiKey" : false,
"n" : 1,
"nscannedObjects" : 100000,
"nscanned" : 100000,
"nscannedObjectsAllPlans" : 100000,
"nscannedAllPlans" : 100000,
"scanAndOrder" : false,
"indexOnly" : false,
"nYields" : 0,
"nChunkSkips" : 0,
"millis" : 48,
"indexBounds" : { },
"server" : "WallE.local:27017"
}
可以看到,这里这次查询只扫描了十万条数据,并且耗费时间大概也只有之前的 十分之一。这是因为,由于限制了本次查询需要获取结果的个数,MongoDB在遍 历数据的过程中一旦发现了找到了结果就直接结束了本次查询,因此效率有了较 大提升。但是这种方式的并不能够解决效率问题,如果需要查询的username为 user999999,那么MongoDB仍然需要遍历整个数据库才能得到结果。
同其他数据库一样,MongoDB也支持索引来提高查询速度,为了提高username的 查询速度,在该字段上建立一个索引:
> db.users.ensureIndex({"username" : 1})
执行完该命令后,就在users这个集合中为username新建了一个索引,这个索引 字段可以在db.system.indexes集合中找到:
> db.system.indexes.find()
{ "v" : 1, "key" : { "_id" : 1 }, "ns" : "test.users", "name" : "_id_" }
{ "v" : 1, "key" : { "username" : 1 }, "ns" : "test.users", "name" : "username_1" }
值得注意的是,从以上查询中可以看到,每个数据集合都有一个默认的索引字段, 就是_id字段,这个字段在该数据集合建立的时候就会创建。
索引建立之后,再来看下执行效率:
> db.users.find({"username": "user99999"}).explain()
{
"cursor" : "BtreeCursor username_1",
"isMultiKey" : false,
"n" : 1,
"nscannedObjects" : 1,
"nscanned" : 1,
"nscannedObjectsAllPlans" : 1,
"nscannedAllPlans" : 1,
"scanAndOrder" : false,
"indexOnly" : false,
"nYields" : 0,
"nChunkSkips" : 0,
"millis" : 0,
"indexBounds" : {
"username" : [
[
"user99999",
"user99999"
]
]
},
"server" : "WallE.local:27017"
}
可以看到,这次MongoDB程序几乎是一瞬间就找到结果,并且扫描的对象个数为1, 可以看到,这次查询直接就找到了需要的结果。
对比第一次没有建立索引时的执行结果,可以看到,第一个字段“cursor”值也有 所变化。作为区分,第一个字段为“BasicCursor”时就表示当前查询没有使用索 引,而建立索引后,该值为“BtreeCursor username_1”,也可以看出来MongoDB 使用的是B树来建立索引。
2 联合索引
通过使用索引,数据库会对数据库中索引中所表示的字段保持已排序状态,也就 是说,我们能够方便的针对该字段进行排序查询如:
> db.users.find().sort({"username" : 1})
...
MongoDB能够很快返回结果,但是这种帮助只能在查询字段在首位的情况下才能 生效,如果该字段不在查询的首位,就可能无法使用到该索引带来的好处了,如:
> db.users.find().sort({"age": 1, "username" : 1})
error: {
"$err" : "too much data for sort() with no index. add an index or specify a smaller limit",
"code" : 10128
}
查询字段第一位为“age”,这个时候,MongoDB就会提示错误信息。
为了解决这类问题,MongoDB同其他数据库一样,也提供了联合索引的操作,同 样通过ensureIndex函数来实现:
> db.users.ensureIndex({"age" : 1, "username" : 1})
执行这个操作可能需要耗费较长时间,执行成功后,仍然可以通过查询 db.system.indexes集合来查看索引建立情况:
> db.system.indexes.find()
{ "v" : 1, "key" : { "_id" : 1 }, "ns" : "test.users", "name" : "_id_" }
{ "v" : 1, "key" : { "username" : 1 }, "ns" : "test.users", "name" : "username_1" }
{ "v" : 1, "key" : { "age" : 1, "username" : 1 }, "ns" : "test.users", "name" : "age_1_username_1" }
可以看到,刚才的操作建立了一个名字为“age_1_username_1”的联合索引,再次 执行刚才的联合查询,就不会提示出错了。
通过建立该索引,数据库中大致会按照如下方式来保存该索引:
... [26, "user1"] -> 0x99887766
[26, "user2"] -> 0x99887722
[26, "user5"] -> 0x73234234 ... [30, "user3"] -> 0x37234234
[30, "user9"] -> 0x33231289 ...
可以看到,索引中第一个字段“age”按照升序排列进行排序,第二个字段 “username”也在第一个字段的范围内按照升序排列。
在ensureIndex函数中,建立索引时,通过将字段索引置为1,可以将索引标识为 升序排列,如果索引置为-1,则将按照降序排列,如:
> db.users.ensureIndex({"age" : -1, "username" : 1})
这样建立的索引“age”字段就将按照降序排列了。
MongoDB如何使用联合索引进行查询,主要是看用户如何执行查询语句,主要有 以下几种情况:
> db.users.find({"age" : 26}).sort({"username" : -1})
这种情况下,由于查询条件指定了“age”的大小,MongoDB可以使用刚才创建的联 合索引直接找到“age”为26的所有项:
... [26, "user1"] -> 0x99887766
[26, "user2"] -> 0x99887722
[26, "user5"] -> 0x73234234 ...
并且由于username也是已经排序了的,因此这个查询可以很快完成。这里需要注 意的是,不管创建“username”索引的时候是使用的升序还是降序,MongoDB可以 直接找到最开始或者最后一项,直接进行数据的遍历,因此这个地方创建索引不 会对查询造成影响。
> db.users.find({"age" : {"$gte" : 18, "lte" : 30}})
这种情况下,MongoDB仍然能够迅速通过联合索引查找到“age”字段在18到30范围 内的所有数据。
最后一种情况较为复杂:
> db.users.find({"age" : {"$gte" : 18, "lte" : 30}}).sort({"username" : -1})
这种情况下,MongoDB首先通过索引查找到“age”范围在18到30之间的所有数据, 由于在这个范围的数据集合中,“username”是未排序的,因此,MongoDB会在内 存中对“username”进行排序,然后将结果输出,如果这个区间中的数据量很大的 话,仍然会出现前面看到的那种一场情况,由于有太多数据需要进行排序操作, 导致程序报错:
error: {
"$err" : "too much data for sort() with no index. add an index or specify a smaller limit",
"code" : 10128
}
这种情况下,可以通过建立一个{"username" : 1, "age" : 1}这样的反向的索 引来帮助进行排序,这个索引建立后,索引大致如下所示:
... ["user0", 69]
["user1", 50]
["user10", 80]
["user100", 48]
["user1000", 111]
["user10000", 98]
["user100000", 21] -> 0x73f0b48d
["user100001", 60]
["user100002", 82]
["user100003", 27] -> 0x0078f55f
["user100004", 22] -> 0x5f0d3088
["user100005", 95] ...
这样,MongoDB可以通过遍历一次这个索引列表来进行排序操作。这样也避免了 在内存中进行大数据的排序操作。
对刚才的查询执行查询计划可以看到:
> db.users.find({"age" : {"$gte" : 21, "$lte" : 30}}).sort({"username" : 1}).explain()
{
"cursor" : "BtreeCursor username_1",
"isMultiKey" : false,
"n" : 83417,
"nscannedObjects" : 1000000,
"nscanned" : 1000000,
"nscannedObjectsAllPlans" : 1002214,
"nscannedAllPlans" : 1002214,
"scanAndOrder" : false,
"indexOnly" : false,
"nYields" : 1,
"nChunkSkips" : 0,
"millis" : 1923,
"indexBounds" : {
"username" : [
[
{
"$minElement" : 1
},
{
"$maxElement" : 1
}
]
]
},
"server" : "WallE.local:27017"
}
使用hint函数,使用反向索引之后的结果如下:
> db.users.find({"age" : {"$gte" : 21, "$lte" : 30}}).sort({"username" : 1}).hint({"username" : 1, "age" : 1}).explain()
{
"cursor" : "BtreeCursor username_1_age_1",
"isMultiKey" : false,
"n" : 83417,
"nscannedObjects" : 83417,
"nscanned" : 984275,
"nscannedObjectsAllPlans" : 83417,
"nscannedAllPlans" : 984275,
"scanAndOrder" : false,
"indexOnly" : false,
"nYields" : 2,
"nChunkSkips" : 0,
"millis" : 3064,
"indexBounds" : {
"username" : [
[
{
"$minElement" : 1
},
{
"$maxElement" : 1
}
]
],
"age" : [
[
21,
30
]
]
},
"server" : "WallE.local:27017"
}
可以看到,第二次执行的时间似乎还要长一些。因此上面介绍的理论并不一定有 效,很多时候,为了提高数据库的查询效率,最好对所有查询语句执行查询计划, 查看执行差异,从而进行优化。
通过上面的例子可以看到在使用联合索引的时候,进行查询操作时,排在前面的 字段如果按照联合索引的字段进行查询,都能够利用到联合索引的优点。
例如,执行如下查询时,“age”字段是{"age" : 1, "username" : 1}的第一个字 段,这个时候就可以使用到这个联合索引进行查询。
> db.users.find({"age" : 99})
例如查询:
> db.users.find({"a" : 10, "b" : 20, "c" : 30})
就可以使用索引:{"a" : 1, "b" : 1, "c" : 1, "d" : 1},只要是按照顺序的 查询都可以利用到索引来进行查询,当然,如果顺序不一致,就无法使用到索引 了,例如:
> db.users.find({"c" : 20, "a" : 10})
就无法使用{"a" : 1, "b" : 1, "c" : 1, "d" : 1}索引带来的好处了。
同关系型数据库一致,在MongoDB执行查询操作时,把最容易进行范围限定的条 件放到最前面,是最有利于查询操作的,排在前面的条件能够筛选的出来的结果 越少,后续的查询效率也就越高。
在MongoDB中,对查询优化采用这样一种方式,当查询条件与索引字段完全一致 时(如查询“i”的字段,同时也存在一个索引为“i”的字段),则MongoDB会直接 使用这个索引进行查询。反之,如果有多个索引可能作用于此次查询,则 MongoDB会采用不同的索引同时并行执行多个查询操作,最先返回100个数据的查 询将会继续进行查询,剩余的查询操作将会被终止。MongoDB会将此次查询进行 缓存,下次查询会继续使用,直到对该数据集进行了一定修改后,再次采用这种 方式进行更新。在执行explain()函数后输出字段中的“allPlans”就表示,所有 尝试进行的查询操作次数。
3 索引类型
在MongoDB中,也可以建立唯一索引:
> db.users.ensureIndex({"username" : 1}, {"unique" : true})
建立了唯一索引后,如果插入相同名称的数据,系统就会报错:
> db.users.insert({"username" : "user1"})
E11000 duplicate key error index: test.users.$username_1 dup key: { : "user1" }
同样的,联合索引也可以建立唯一索引:
> db.users.ensureIndex({"age" : 1, "username" : 1}, {"unique" : true})
创建成功后,如果插入相同的数据内容同样会报错。
如果数据库中已经包含了重复数据,可以通过创建唯一索引的方式来进行删除。 但是注意,这种方式非常危险,如果不是确定数据无效,不能这样操作,因为, MongoDB只会保留遇到的第一个不同的数据项,后续重复数据都将被删除:
> db.users.ensureIndex({"age" : 1, "username" : 1}, {"unique" : true, "dropDups" : true})
某些时候,我们希望对数据库中某个字段建立唯一索引,但是又不一定是每条数 据都包含这个字段,这个时候,可以使用sparse索引来解决这个问题:
> db.users.ensureIndex({"email" : 1}, {"unique" : true, "sparse" : 1})
如果存在如下数据:
> db.foo.find()
{ "_id" : 0 }
{ "_id" : 1, "x" : 1 }
{ "_id" : 2, "x" : 2 }
{ "_id" : 3, "x" : 3 }
当没有建立索引的情况下,执行如下操作会返回:
> db.foo.find({"x" : {"$ne" : 2}})
{ "_id" : 0 }
{ "_id" : 1, "x" : 1 }
{ "_id" : 3, "x" : 3 }
如果建立了sparse索引,则MongoDB就不会返回第一条数据,而是返回所有包含 “x”字段的数据:
> db.foo.find({"x" : {"$ne" : 2}})
{ "_id" : 0 }
{ "_id" : 1, "x" : 1 }
{ "_id" : 3, "x" : 3 }
4 索引管理
通过执行getIndexes()函数,可以获得当前数据集中所有的索引:
> db.users.getIndexes()
[
{
"v" : 1,
"key" : {
"_id" : 1
},
"ns" : "test.users",
"name" : "_id_"
},
{
"v" : 1,
"key" : {
"age" : 1,
"username" : 1
},
"ns" : "test.users",
"name" : "age_1_username_1"
},
{
"v" : 1,
"key" : {
"username" : 1,
"age" : 1
},
"ns" : "test.users",
"name" : "username_1_age_1"
},
{
"v" : 1,
"key" : {
"username" : 1
},
"unique" : true,
"ns" : "test.users",
"name" : "username_1"
}
]
其中的“name”字段可以用于对索引的删除操作:
> db.users.dropIndex("username_1_age_1")
就将删除{"username" : 1, "age" : 1}这个索引。
MongoDB索引的使用的更多相关文章
- [DataBase] MongoDB (7) MongoDB 索引
MongoDB 索引 1. 建立索引 唯一索引db.passport.ensureIndex( {"loginname": 1}, {"unique": tru ...
- MongoDB索引介绍
MongoDB中的索引其实类似于关系型数据库,都是为了提高查询和排序的效率的,并且实现原理也基本一致.由于集合中的键(字段)可以是普通数据类型,也可以是子文档.MongoDB可以在各种类型的键上创建索 ...
- MongoDB(索引及C#如何操作MongoDB)(转载)
MongoDB(索引及C如何操作MongoDB) 索引总概况 db.test.ensureIndex({"username":1})//创建索引 db.test.ensureInd ...
- MongoDB索引(一)
原文地址 一.介绍 我们已经很清楚索引会提高查询效率.如果没有索引,MongoDB必须对全部集合进行扫描,即,扫描集合中每条文档以选择那些符合查询条件的文档.对查询来说如果存在合适的索引,则Mongo ...
- MongoDB 索引篇
MongoDB 索引篇 索引的简介 索引可以加快查询的速度,但是过多的索引或者规范不好的索引也会影响到查询的速度.且添加索引之后的对文档的删除,修改会比以前速度慢.因为在进行修改的时候会对索引进行更新 ...
- MongoDB索引的种类与使用
一:索引的种类 1:_id索引:是绝大多数集合默认建立的索引,对于每个插入的数据,MongoDB都会自动生成一条唯一的_id字段2:单键索引: 1.单键索引是最普通的索引 2.与_id索引不同,单键索 ...
- MongoDB索引,性能分析
索引的限制: 索引名称不能超过128个字符 每个集合不能超过64个索引 复合索引不能超过31列 MongoDB 索引语法 db.collection.createIndex({ <field&g ...
- MongoDB索引原理
转自:http://www.mongoing.com/archives/2797 为什么需要索引? 当你抱怨MongoDB集合查询效率低的时候,可能你就需要考虑使用索引了,为了方便后续介绍,先科普下M ...
- MongoDB · 引擎特性 · MongoDB索引原理
MongoDB · 引擎特性 · MongoDB索引原理数据库内核月报原文链接 http://mysql.taobao.org/monthly/2018/09/06/ 为什么需要索引?当你抱怨Mong ...
- MongoDB索引管理
一.创建索引 创建索引使用db.collectionName.ensureIndex(...)方法进行创建: 语法: >db.COLLECTION_NAME.ensureIndex({KEY:1 ...
随机推荐
- mysql-5.7.14 源码安装笔记
安装编译 下载源码 mysql-5.7.14.tar.gz 解压定义安装变量 cd /usr/local/src mysql_version="mysql-5.7.14" tar ...
- Qt 程序访问 sqlite 权限错误
在Linux桌面上开发应用,想要拥有root权限,可是又需要弹窗申请.所以尽量避免这种情况发生. 另外:gksu,pkexec可以提供gui的root权限索取功能. 因为db文件是安装的时候放到etc ...
- 锁的封装 读写锁、lock
最近由于项目上面建议使用读写锁,而去除常见的lock锁.然后就按照需求封装了下锁.以简化锁的使用.但是开发C#的童鞋都知道lock关键字用起太方便了,但是lock关键字不支持超时处理.很无奈,为了实现 ...
- window下flask开发环境搭建
1.安装python 官网下载https://www.python.org/downloads/,按提示安装就行,记住安装目录,把它添加到系统path中. 2.安装pip 官网下载pip文件:http ...
- eclipse生成uml
安装eclipse插件在help->Install new software里面add 有本地和网络两种 1.ModelGoon 该插件需要在file中new ModelGoon Diagram ...
- crack.vbs病毒,优盘里所有文件全变成快捷方式
去了一趟学校打印店,用优盘copy打印了点东西,当时在打印店电脑里打开优盘的时候里面就变成了快捷方式,但没怎么在意.回来之后在自己电脑上居然也这样了.网上一搜是中了crack.vbs病毒了.格式化优盘 ...
- Mac 下安装ruby,以及CocoaPods安装以及使用网摘
ruby安装网址:https://ruby-china.org/wiki/install_ruby_guide CocoaPods安装和使用教程地址:http://code4app.com/artic ...
- iOS开发中的各种错误
提交iTunesconnect遇到的问题: 1. error itms-90179 Invalid Code Signing. 解决:发现是发布正式被撤销了,重新生成发布Certificates,重新 ...
- SVM系列之拉格朗日对偶
在学习SVM(Support Vector Machine) 支持向量机时,对于线性可分的分类样本求出的分类函数为: 其中,分类超平面可以表示为:
- 关于git(分布式版本控制系统)的一些操作和命令
很久没上博客写东西了,今天闲着无聊就不想敲代码,所以看了看有没有一些好玩的东西,这不让我发现了git这个源代码版本控制工具,话不多说,就让我来简单的分享一下它的一些操作和命令以及命令所执行的含义吧! ...