您的位置:首页 > 数据库 > Mongodb

学习MongoDB--(5-1):索引(简介、使用)

2012-08-10 17:18 302 查看
我们在使用关系型数据库时都或多或少接触过索引(index),MongoDB作为一种数据库,同样也提供了对索引的支持!索引存在的最主要目的就是加快查询速度!数据库的数据和其索引可以对应实际中我们使用的字典以及字典前面的索引部分。我们使用字典查询一个字时,会先在区区几页的索引中按照某种顺序进行定位,然后再直接翻到数百页字典的某一页。这个过程也是数据库查询数据的过程!数据库会按照索引对数据进行一个排序,存储在一个地方,查询时先到这个地方进行定位,然后再去取真实数据!

我们前面演示的各种查询并没有创建使用索引(除了键“_id”的查询,系统会为键“_id”创建唯一索引),MongoDB对不会采用任何索引的查询都会进行“全表扫描”,即查询整个集合。对于大的集合,效率会很低,通常我们要避免“全表扫描”的出现!所以学习使用索引还是非常有必要的!

MongoDB中通过调用集合的ensureIndex函数来构建索引,即索引是建立在集合之上的。我们看一个例子:

> db.user.find();
{ "_id" : ObjectId("5020faf5d6acd1b2a3fb316f"), "name" : "tim", "age" : 40, "registered" : ISODate("2007-03-02T16:00:00Z") }
{ "_id" : ObjectId("5020fb08d6acd1b2a3fb3170"), "name" : "tom", "age" : 29, "registered" : ISODate("2009-07-02T16:00:00Z") }
{ "_id" : ObjectId("5020fb27d6acd1b2a3fb3171"), "name" : "jimmy", "age" : 18, "registered" : ISODate("2009-09-02T16:00:00Z") }
> db.user.ensureIndex({"name":1, "age":-1});
>

ensureIndex函数的参数和前面讲得游标的sort函数差不多,都是一个文档,使用对应集合中的键,其值表示构建索引的方向。值>0(通常为1)表示按升序构建索引数据,值<0(通常为-1)表示按降序构建索引数据。上面我们建了一个包含两个键的索引。

如果我们构建含有多个键的索引,那么是不是我们查询时必须使用构建索引的所有键,这个索引才会生效呢?这个是错误的!但要想要这个多键索引生效,也是有要求的,就是按顺序使用构建索引的一部分键或全部键,索引才会生效!这句话的意思就是:假如我们构建这样一个索引:{“a”:1, "b":1, "c":1,"d":1},我们其时顺带构建了如下3个索引{"a":1},{“a”:1, “b”:1},{"a":1,"b":1,“c”:1}。只要符合这几个索引的查询都会触发这个复合索引!注意,MongoDB的查询优化器会按照索引中键的顺序优化我们的查询!所以对于上述索引:查询条件{"a":***},{“a”:***,
“b”:***},{“a”:***,“c”:***},{“d”:***, “a”:***}都会触发索引,但查询条件{"b":***},{”b“:***,”c“:***}不会使用这个索引!

上面讲了索引给我们带来的好处,同样,索引还会给我们带来不小的坏处!使用索引后,每次对集合的插入、更新、删除都会因为要更新索引而产生额外的开销!所以通常索引适用于一次插入,多次查询,更新不是很频繁的集合,并且要做到索引高效使用,不要过多地创建索引!这里强调一下,禁止为每一个键创建索引!这样的集合插入速度会非常慢!MongoDB中规定一个集合最多有64个索引!这绝对是足够的!

创建索引使用的键应该有较多的值,过少的值会导致索引不实用!如你在一个布尔类型的键(就true、false两种值)上创建索引,按照这个键的查询通常会返回一半以上的数据,使用这种索引反而会降低效率!有个规律,如果查询最后返回的结果的数量>目标集合文档数量的一半,使用索引反而不如“全表扫描”!

我们在实际使用中,构建索引时,要考虑如下3个问题:

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

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

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

对于上述1,2点还是比较好理解的,对于第3点,我们可以举一个例子,我们有一个集合{"user":***,"state":***,“date”:***},每个用户每天都会有几十条状态的更新!我们为应对 “对特定用户最新状态的查询” 建立了如下一个索引{"user":1,“date”:-1}。但当这个集合数据量过大时,这个索引作用已经不大了!因为每个用户都会有上万条的陈旧状态信息,这些陈旧的状态信息因为索引首先按用户名排序而排在前部!这会导致查询用户名比较靠后的用户最新状态的索引信息就很耗时!如果我们将索引改为{"date":-1,“user”:1},这样的索引会更适合
“对特定用户最新状态的查询” 这种需求!因为他可以保证索引前面对应的就是常用数据!

【构建内嵌文档的索引】

为内嵌文档建立索引和上述为普通的键创建索引是一样的!内嵌文档的键通过点表示法表示即可!如:

> 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.ensureIndex({"comment.author":1});
>

内嵌文档的键可以和普通键一起构建复合索引!

【为排序创建索引】

当数据量太大时,有时我们可能单纯是为了查询中排序去做索引!如果我们在查询中使用没有创建索引的键来排序,MongoDB会将所有的数据取到内存中来排序!因此排序首先限制于内存的大小并且MongoDB本身对无索引排序的数据量也是有要求的,即T级别的数据无法在内存中进行排序!否则MongoDB会报错!因此如果我们要对超大数据集按某个键排序,我们就要为这个键构建索引!MongoDB会按照索引顺序提取数据,这样就不会耗尽内存!

【索引名称】

集合中每个索引都会有一个字符串名字,来唯一标示这个索引!MongoDB通过这个名称来操作索引!默认索引的名称规则为:keyname1_dir1_keyname2_dir2.....;keyname是构建索引的键名称,dir是代表其方向的数字!如我们创建索引{"a":1,“b”:1},其对应的默认名称是:"a_1_b_1"。我们在创建索引时也可以为索引指定名称:

> db.user.ensureIndex({"name":1,"age":1,"registered":-1}, {"name":"myuserIndex"});
>

通过为ensureIndex函数使用第二个文档参数,参数中指明键“name”的值即可!索引名称的长度是有限制的,如果我们以后要创建一个非常复杂包含很多键的索引,手动指定唯一性的索引名称是很有必要的,这也方便我们后期对索引进行维护!

【创建唯一索引】

唯一索引可以确保集合中某一个指定键在每一个文档中的值都是唯一的!如下面例子,我们要保证用户的姓名是唯一的,我们可以对姓名来创建唯一性索引,insert在插入时会进行检测,如果该值已经存在,就报错:

> db.user.find();
{ "_id" : ObjectId("5020faf5d6acd1b2a3fb316f"), "name" : "tim", "age" : 40, "registered" : ISODate("2007-03-02T16:00:00Z") }
{ "_id" : ObjectId("5020fb08d6acd1b2a3fb3170"), "name" : "tom", "age" : 29, "registered" : ISODate("2009-07-02T16:00:00Z") }
{ "_id" : ObjectId("5020fb27d6acd1b2a3fb3171"), "name" : "jimmy", "age" : 18, "registered" : ISODate("2009-09-02T16:00:00Z") }
> db.user.ensureIndex({"name":1},{"unique":true});
> db.user.insert({"name":"tim","age":33,"registered":new Date()});
E11000 duplicate key error index: mylearndb.user.$name_1  dup key: { : "tim" }
> db.user.update({"name":"tom"},
... {"$set":{"name":"tim"}});
E11000 duplicate key error index: mylearndb.user.$name_1  dup key: { : "tim" }
>

创建唯一想索引还是要启用ensureIndex 的第二个文档参数,这时这个参数中使用的键是"unique",其值为true即可!这里我们可以看到,唯一性索引会阻止insert和update这种产生重复值的操作!

我们这里强调一下,如果一个文档中没有唯一性索引对应的键,则这个文档默认该键的值为null,如果插入多条这种文档,后面的文档因为值同为null,违反了唯一性约束而插入失败!

【消除重复】

实际情况中,我们为一个集合创建索引时,集合中通常已经存在了很多数据!如果这是我们要为某一个键创建唯一索引,就有可能失败,因为这个键的值此时在该集合中可能已经重复了,如:

> db.user.find();
{ "_id" : ObjectId("5020faf5d6acd1b2a3fb316f"), "name" : "tim", "age" : 40, "registered" : ISODate("2007-03-02T16:00:00Z") }
{ "_id" : ObjectId("5020fb08d6acd1b2a3fb3170"), "name" : "tom", "age" : 29, "registered" : ISODate("2009-07-02T16:00:00Z") }
{ "_id" : ObjectId("5020fb27d6acd1b2a3fb3171"), "name" : "jimmy", "age" : 18, "registered" : ISODate("2009-09-02T16:00:00Z") }
{ "_id" : ObjectId("5024d001db6b44118615d3c3"), "name" : "tim", "age" : 33, "registered" : ISODate("2012-08-10T09:10:25.579Z") }
> db.user.ensureIndex({"name":1},{"unique":true});
E11000 duplicate key error index: mylearndb.user.$name_1  dup key: { : "tim" }
>

我们用户集合中存在两条键“name”为“tim”的文档,如果此时我们要为键”name“创建唯一索引就会失败!那么我们该如何做呢?MongoDB会我们提供了删除重复这种功能选项,”dropDups“!使用这种选项创建唯一性索引,会保留这个键第一次出现某个值的文档,再次出现这个值的文档会直接被删掉,还是这个例子:

> db.user.find();
{ "_id" : ObjectId("5020faf5d6acd1b2a3fb316f"), "name" : "tim", "age" : 40, "registered" : ISODate("2007-03-02T16:00:00Z") }
{ "_id" : ObjectId("5020fb08d6acd1b2a3fb3170"), "name" : "tom", "age" : 29, "registered" : ISODate("2009-07-02T16:00:00Z") }
{ "_id" : ObjectId("5020fb27d6acd1b2a3fb3171"), "name" : "jimmy", "age" : 18, "registered" : ISODate("2009-09-02T16:00:00Z") }
{ "_id" : ObjectId("5024d001db6b44118615d3c3"), "name" : "tim", "age" : 33, "registered" : ISODate("2012-08-10T09:10:25.579Z") }
> db.user.ensureIndex({"name":1},{"unique":true});
E11000 duplicate key error index: mylearndb.user.$name_1  dup key: { : "tim" }
> db.user.ensureIndex({"name":1},{"unique":true, "dropDups":true});
> db.user.find();
{ "_id" : ObjectId("5020faf5d6acd1b2a3fb316f"), "name" : "tim", "age" : 40, "registered" : ISODate("2007-03-02T16:00:00Z") }
{ "_id" : ObjectId("5020fb08d6acd1b2a3fb3170"), "name" : "tom", "age" : 29, "registered" : ISODate("2009-07-02T16:00:00Z") }
{ "_id" : ObjectId("5020fb27d6acd1b2a3fb3171"), "name" : "jimmy", "age" : 18, "registered" : ISODate("2009-09-02T16:00:00Z") }
>

在创建唯一性索引时,第二个文档参数加了"dropDups":true 的选项!索引创建成功,我们再看这个集合时,有一个文档已经被删掉了!这种方式非常鲁莽,对于一些重要数据,我们可能需要先进行备份再如上处理!

【复合唯一性索引】

就是要求一个复合索引所有键的值不能同时重复,单个键重复无所谓!这个和上述单键的唯一性索引是一样的,这里就不做过多解释!
内容来自用户分享和网络整理,不保证内容的准确性,如有侵权内容,可联系管理员处理 点击这里给我发消息
标签: