MongoDB中的索引和其他数据库索引类似,也是使用B-Tree结构。MongoDB的索引是在collection级别上的,并且支持在任何列或者集合内的文档的子列中创建索引。
下面是官方给出的一个使用索引查询和排序的一个结构图。
所有的MongoDB集合默认都有一个唯一索引在字段“_id”上,如果应用程序没有为 “_id”列定义一个值,MongoDB将创建一个带有ObjectId值的列。(ObjectId是基于 时间、计算机ID、进程ID、本地进程计数器 生成的)
MongoDB 同样支持在一列或多列上创建升序或降序索引。
MongoDB还可以创建 多键索引、数组索引、空间索引、text索引、哈希索引,其属性可以是唯一性索引、稀疏性索引、TTL(time to live)索引。
索引的限制:
索引名称不能超过128个字符
每个集合不能超过64个索引
复合索引不能超过31列
MongoDB 索引语法 | |
db.collection.createIndex({ <field>: < 1 or -1 > }) db.collection.ensureIndex({ <field>: < 1 or -1 > })
db.collection.createIndex( { "filed": sort } ) db.collection.createIndex( { "filed": sort , "filed2": sort } )
db.tab.ensureIndex({"id":1}) db.tab.ensureIndex({"id":1} ,{ name:"id_ind"}) db.tab.ensureIndex({"id":1,"name":1},{background:1,unique:1}) db.tab.ensureIndex( { "id" : "hashed" })
| 创建索引(两种方法)
filed :为键列 sort :为排序。1 为升序;-1为降序。
创建单列索引 创建索引并给定索引名称 后台创建唯一的复合索引 创建哈希索引 (更多参数 看文章底部) |
db.tab.indexStats( { index: "id_ind" } ) db.runCommand( { indexStats: "tab", index: "id_ind" } ) db.tab.getIndexes() db.system.indexes.find() | (前2个似乎不能用,官方文档解释) (not intended for production deployments) 查看索引 |
db.tab.totalIndexSize(); | 查看索引大小 |
db.tab.reIndex() db.runCommand({reIndex:"tab"}) | 重建索引 |
db.tab.dropIndex(<indexname>) db.tab.dropIndex("id_1") db.tab.dropIndexes() | 删除索引 <indexname>为getIndexes看到的索引名称 删除所有索引(注意!) |
|
|
索引性能测试:
查看索引是否生效,分析查询性能有没有提高。先插入10万数据到集合tab
for(var i=0;1<=100000;i++){
var value=parseInt(i*Math.random());
db.tab.insert({"id":i,"name":"kk"+i,"value":value});
}
不知道是不是虚拟机的原因,插入了10分钟都未完成!~
自己又打开文件夹查看,一直进不去文件夹。结果客户端连接断开了!~查看服务竟然停了!
重启服务,进去查看行数:96万!(过后再查看吧!就用这数据测试了!)
db.tab.find().count()
AnalyzeQuery Performance :http://docs.mongodb.org/manual/tutorial/analyze-query-plan/
分析函数 | |
db.tab.find({"name":"kk50000"}).explain() | 查询name=”kk50000”的执行分析 |
db.tab.find({"name":"kk50000"}).explain("queryPlanner") db.tab.find({"name":"kk50000"}).explain("Verbosity") db.tab.find({"name":"kk50000"}).explain("executionStats") db.tab.find({"name":"kk50000"}).explain("allPlansExecution") | 这3种方法执行结果完全包括上面这种的结果 |
db.tab.find({"name":"kk50000"}).explain() 结果做分析: | |
"cursor" : "BasicCursor", "isMultiKey" : false, "n" : 1, "nscannedObjects" : 966423, "nscanned" : 966423, "nscannedObjectsAllPlans" : 966423, "nscannedAllPlans" : 966423, "scanAndOrder" : false, "indexOnly" : false, "nYields" : 7555, "nChunkSkips" : 0, "millis" : 4677, "server" : "kk-ad:27017", "filterSet" : false | 游标类型。BasicCurso(扫描), BtreeCursor(索引) 是否多键(组合)索引 返回行数 扫描行数 扫描行数 所有计划扫描的次数 所有计划扫描的次数 是否在内存中排序
耗时(毫秒) 服务器
|
现在创建索引:
db.tab.createIndex({"name":1})
db.tab.find({"name":"kk50000"}).explain() 使用索引的结果 | |
"cursor" : "BtreeCursor name_1", "isMultiKey" : false, "n" : 1, "nscannedObjects" : 1, "nscanned" : 1, "nscannedObjectsAllPlans" : 1, "nscannedAllPlans" : 1, "scanAndOrder" : false, "indexOnly" : false, "nYields" : 0, "nChunkSkips" : 0, "millis" : 1, "indexBounds" : { "name" : [ [ "kk50000", "kk50000" ] ] }, "server" : "kk-ad:27017", "filterSet" : false | 游标使用索引BtreeCursor = name_1
耗时:1毫秒
|
上面可以看到,没使用索引时,耗时4677毫秒,使用索引后,1毫秒!~并且不用全文档扫描。
索引提示(hint),当前collection创建的索引:
db.tab.ensureIndex({"id":1} ,{name:"id_ind"})
db.tab.ensureIndex({"id":1,"name":1},{background:1,unique:1})
db.tab.ensureIndex( { "name" :"hashed" })
现在查询 id=5000 的行(结果集为1行)
db.tab.find({"id": 5000}).explain()
查询使用的是id和name的复合索引。
"nscannedObjectsAllPlans" : 2,
"nscannedAllPlans" : 2,
现在加上索引提示,强制使用索引:
db.tab.find({"id": 5000}).hint({"id":1}).explain()
这时使用的是单个键列为id的索引。
"nscannedObjectsAllPlans" : 1,
"nscannedAllPlans" : 1,
上面还可以看到,索引有个边界值“indexBounds”
这个边界值在复合索引查询的时候,会导致扫描更多的数据。这是一个bug :wrong index ranges when using compound index on a list
当然我们也可以自己限制边界值。
db.tab.find().min({"id":5000}).max({ "id":5005})
从上面看,实际只查询这个边界的内的数值。再查看执行计划:
db.tab.find().min({"id":5000}).max({ "id":5005}).explain()
只是5行数据。如果查询id=5000的,但是索引边界又有问题,这时可以限制边界,如:
db.tab.find({"id": 5000 }).min({"id":5000}).max({ "id":5005})
在索引方法中,还有一个方法为cursor.snapshot(),它会确保查询不会多次返回相同的文档,即使是写操作在一个因为文档大小增长而移动的文档。但是,snapshot()不能保证插入或者删除的隔离性。snapshot()是使用在_id键列上的索引,因此snapshot()不能使用sort() 或 hint()。
分快照函数析snapshot()的查询结果:
db.tab.find({"id": 5000}).snapshot().explain()
虽然使用了索引“_id”,但是把整个集合都搜索了!~
加索引提示看看,应该是报错的:
db.tab.find({"id": 5000}).snapshot().hint({"id":1})
果然是出错:snapshot 不能使用提示。
下面总结索引查询的一些方法:
Indexing Query Modifiers | |
db.tab.find({"id": 5000 }).hint({"id":1}) db.tab.find({"id": 5000 })._addSpecial("$hint",{"id":1}) db.tab.find({ $query: {"id": 5000 }, $hint: { "id":1 }}) | 使用键列id的索引查询id=5000的结果 |
db.tab.find({"id": 5000 }).snapshot() db.tab.find({"id": 5000 })._addSpecial( "$snapshot", true ) db.tab.find({ $query: {"id": 5000 }, $snapshot: true }) | 使用快照的查询id=5000的结果 |
db.tab.find({"id": 5000 }).hint({"id":1}).explain() db.tab.find({"id": 5000})._addSpecial("$explain",1) db.tab.find({ $query: {"id": 5000 }, $hint: { "id":1 }, $explain: 1}) | 查看执行计划信息 |
索引边界设置 | |
db.tab.find({"id": 5000 }).max({ "id":5005}) db.tab.find({ $query:{"id": 5000 },$max:{ "id": 5005}}) db.tab.find({"id": 5000 })._addSpecial("$max",{"id": 5005})
db.tab.find({"id": 5000 }).min({ "id":5000}).max({ "id":5005}).explain() db.tab.find({ $query:{"id": 5000 },$max:{ "id": 5005},$min:{ "id": 5000}}) db.tab.find({"id": 5000 })._addSpecial("$min",{"id": 5000})._addSpecial("$max",{"id": 5005}) |
摘取了这了的一个总结:http://www.w3cschool.cc/mongodb/mongodb-indexing.html
Parameter | Type | Description |
background | Boolean | 建索引过程会阻塞其它数据库操作,background可指定以后台方式创建索引,即增加 "background" 可选参数。 "background" 默认值为false。 |
unique | Boolean | 建立的索引是否唯一。指定为true创建唯一索引。默认值为false. |
name | string | 索引的名称。如果未指定,MongoDB的通过连接索引的字段名和排序顺序生成一个索引名称。 |
dropDups | Boolean | 在建立唯一索引时是否删除重复记录,指定 true 创建唯一索引。默认值为 false. |
sparse | Boolean | 对文档中不存在的字段数据不启用索引;这个参数需要特别注意,如果设置为true的话,在索引字段中不会查询出不包含对应字段的文档.。默认值为 false. |
expireAfterSeconds | integer | 指定一个以秒为单位的数值,完成 TTL设定,设定集合的生存时间。 |
v | index version | 索引的版本号。默认的索引版本取决于mongod创建索引时运行的版本。 |
weights | document | 索引权重值,数值在 1 到 99,999 之间,表示该索引相对于其他索引字段的得分权重。 |
default_language | string | 对于文本索引,该参数决定了停用词及词干和词器的规则的列表。 默认为英语 |
language_override | string | 对于文本索引,该参数指定了包含在文档中的字段名,语言覆盖默认的language,默认值为 language. |