MongoDB学习笔记5:索引

1. 索引简介

    索引就是用来加速查询的。MongoDB就是使用ensureIndex来创建索引,创建索引后,MongoDB组织集合的方式会发生变化:

> db.c.find().limit(3)
{ "_id" : ObjectId("55079008f6ee9030e868d86e"), "x" : 0 }
{ "_id" : ObjectId("55079008f6ee9030e868d86f"), "x" : 1 }
{ "_id" : ObjectId("55079008f6ee9030e868d870"), "x" : 2 }
    当我们执行索引后(-1代表降序,1代表升序):
> db.c.ensureIndex({"x" : -1})
{
	"createdCollectionAutomatically" : false,
	"numIndexesBefore" : 2,
	"numIndexesAfter" : 2,
	"note" : "all indexes already exist",
	"ok" : 1
}
集合内部的组织变成了:
{ "_id" : ObjectId("55079008f6ee9030e868d86e"), "x" : 2 }
{ "_id" : ObjectId("55079008f6ee9030e868d86f"), "x" : 1 }
{ "_id" : ObjectId("55079008f6ee9030e868d870"), "x" : 0 }
    但是,它们显示却没有多少改变:
> db.c.find().limit(3)
{ "_id" : ObjectId("55079008f6ee9030e868d86e"), "x" : 0 }
{ "_id" : ObjectId("55079008f6ee9030e868d86f"), "x" : 1 }
{ "_id" : ObjectId("55079008f6ee9030e868d870"), "x" : 2 }

1. 扩展索引

    索引应该避免关联过于频繁的键值。考虑我们有一个集合,保存了用户的状态信息。现在想要查询用户和日期,取出某一个用户最近的状态,则我们可以这样创建索引:

> db.status.ensureIndex({"user" : 1, "date" : -1})
    但是这种扩展索引的方法有一个很大的弊端是:假设有数百万的用户,没人每天都有数十条状态的更新,那么这个索引反而是一个累赘,因为它是先用user来建立索引的。

    我们应该修改为:

> db.status.ensureIndex({"date" : -1, "user" : 1})
所以,建立索引时要考虑如下问题:

1. 会做什么样的查询?其中哪些键需要索引?

2. 每个键的索引方向是怎样的?

3. 如何应对扩展?有没有不同的键的排列可以使常用数据更多的保留在内存中。

2. 索引内嵌文档中的键

    为内嵌文档的键建立索引和为普通的键创建索引没有什么区别:

> db.blog.ensureIndex({"comments.date" : 1})
{
	"createdCollectionAutomatically" : true,
	"numIndexesBefore" : 1,
	"numIndexesAfter" : 2,
	"ok" : 1
}

3. 为排序创建索引

    随着集合的增长,需要针对查询中大量的排序做索引。如果对没有索引的键调用sort,MongoDB需要将所有数据提取到内存来排序。因此,可以做无索引排序是有一个上限的,那就是不可能在内存里面做T级别数据的排序。一旦集合大到不能在内存中排序,MongoDB就会报错。

    按照排序来索引以便让MongoDB按照顺序提取数据,这样就能排序大规模数据,而不必担心用光内存。

2. 唯一索引

    唯一索引可以确保集合的每一个文档的指定键都有唯一值:

> db.people.ensureIndex({"word" : 1}, {"unique" : true})
{
	"createdCollectionAutomatically" : false,
	"numIndexesBefore" : 3,
	"numIndexesAfter" : 4,
	"ok" : 1
}
> db.people.find()
{ "_id" : ObjectId("5507c134c5bce3f400b5c787"), "word" : "a", "num" : 1 }
{ "_id" : ObjectId("5507c13ac5bce3f400b5c788"), "word" : "b", "num" : 3 }
{ "_id" : ObjectId("5507c141c5bce3f400b5c789"), "word" : "c", "num" : 5 }
{ "_id" : ObjectId("5507c148c5bce3f400b5c78a"), "word" : "d", "num" : 2 }
{ "_id" : ObjectId("5507c14fc5bce3f400b5c78b"), "word" : "e", "num" : 4 }
> db.people.insert({"word" : "a", "num" : 10})
WriteResult({
	"nInserted" : 0,
	"writeError" : {
		"code" : 11000,
		"errmsg" : "E11000 duplicate key error index: mydb.people.$word_1 dup key: { : \"a\" }"
	}
})

1. 消除重复

    如果已有的集合中存在重复,我们需要dropDups选项保留发现的第一个文档,而删除接下来的重复值的文档:

> db.a.find()
{ "_id" : ObjectId("5507c94acdf38f5d77984d05"), "x" : 1 }
{ "_id" : ObjectId("5507c951cdf38f5d77984d06"), "x" : 1 }
{ "_id" : ObjectId("5507c964cdf38f5d77984d07"), "x" : 2 }
> db.a.ensureIndex({x : 1}, {unique : 1, dropDups : 1})
{
	"createdCollectionAutomatically" : false,
	"numIndexesBefore" : 1,
	"errmsg" : "exception: E11000 duplicate key error index: mydb.a.$x_1 dup key: { : 1.0 }",
	"code" : 11000,
	"ok" : 0
}
    但是我使用的时候,却报错,不知道为什么。

3. 使用explain和hint

    explain是一个非常有用的工具,会帮助你获得查询方面诸多有用的信息。只要对游标调用该方法,就可以得到查询细节。explain会返回一个文档,而不是游标本身,这是与多数游标方法不同之处。

> db.foo.find()
{ "_id" : ObjectId("55078dbff6ee9030e868d86c"), "apple" : 1, "banana" : 6, "peach" : 3 }
{ "_id" : ObjectId("55078dd4f6ee9030e868d86d"), "apple" : 8, "spinach" : 4, "watermelon" : 4 }
> db.foo.find().explain()
{
	"queryPlanner" : {
		"plannerVersion" : 1,
		"namespace" : "mydb.foo",
		"indexFilterSet" : false,
		"parsedQuery" : {
			"$and" : [ ]
		},
		"winningPlan" : {
			"stage" : "COLLSCAN",
			"filter" : {
				"$and" : [ ]
			},
			"direction" : "forward"
		},
		"rejectedPlans" : [ ]
	},
	"serverInfo" : {
		"host" : "leichaojian-ThinkPad-T430",
		"port" : 27017,
		"version" : "3.0.1",
		"gitVersion" : "534b5a3f9d10f00cd27737fbcd951032248b5952"
	},
	"ok" : 1
}
    explain会返回查询使用的索引情况,耗时及扫描文档数的统计信息。

备注: 书上的例子和我所使用的MongoDB版本3.0.1的输出有很大的出入,所以不做笔记。

4. 索引管理

    索引的元信息存储在每一个数据库的system.indexes集合中。这是一个保留集合,不能对其插入或者删除文档。操作只能通过ensureIndex或者dropIndexes进行。

你可能感兴趣的:(MongoDB学习笔记5:索引)