索引就是用来加速查询的。数据库索引与书籍的索引类似:有了索引就不需要翻遍整本书,数据库则可以直接在索引中查找,使得查找速度能提高几个数量级。在索引中找到条目以后,就可以直接跳转到目标文档的位置。
5.1 索引简介
现在要依照某个键进行查找:
- [root@localhost bin]# ./mongo
- MongoDB shell version: 2.0.2
- connecting to: test
- > db.people.find();
- { "_id" : ObjectId("503e45f77d2cae668c71acbf"), "name" : "joe", "random" : 0.8444473752020685 }
- { "_id" : ObjectId("503e45fe7d2cae668c71acc0"), "name" : "john", "random" : 0.04516828536238293 }
- { "_id" : ObjectId("503e46027d2cae668c71acc1"), "name" : "jim", "random" : 0.896214824631229 }
- >
当查询中仅使用一个键时,可以对该键建立索引,以提高查询速度。本例中,对"username"建立索引。创建索引要使用ensureIndex方法:
- > db.people.ensureIndex({"name":1})
对于同一个集合,同样的索引只需要创建一次。反复创建是徒劳的。
对某个键创建的索引会加速对该键的查询。然而,对于其他查询可能没有帮助,即便是查询包含了被索引的键。例如,下面的查询就不会从先前建立索引中获得任何的性能提升。
>db.people.find({"date":date1}).sort({"date":1,"username":1})
服务器必须“查找整本书”找到想要的日期。这个过称作表扫描,就是在没有索引的书中查找内容,要从第一页开始,从前翻到后。通常来说,要尽量避免让服务器做表扫描,因为当集合很大时会非常慢。
时间证明,一定要创建查询中用到的所有键的索引。例如,对于上面的查询,应该建立日期和用户名的索引:
db.ensureIndex({"date":1,"username":1})
传递给ensureIndex的文档其形式与传递给sort的文档形式一样:一组值为1或者-1的键,表示索引创建的方向。若索引只有一个键,则方向无关紧要。单键索引有点像一个按照字母顺序组织的书籍索引:无论从A到Z,还是从Z到A,显然都要从M开始查找。
- > db.people.remove();
- > db.people.insert({"username":"smith","age":48,"user_id":0})
- > db.people.insert({"username":"smith","age":30,"user_id":1})
- > db.people.insert({"username":"john","age":36,"user_id":2})
- > db.people.insert({"username":"john","age":18,"user_id":3})
- > db.people.insert({"username":"joe","age":36,"user_id":4})
- > db.people.insert({"username":"john","age":7,"user_id":5})
- > db.people.insert({"username":"simon","age":3,"user_id":6})
- > db.people.insert({"username":"joe","age":27,"user_id":7})
- > db.people.insert({"username":"jacob","age":17,"user_id":8})
- > db.people.insert({"username":"sally","age":52,"user_id":9})
- > db.people.insert({"username":"simon","age":59,"user_id":10})
- >
若有多个键,就得考虑索引的方向问题了。比如以{"username":1,"age":-1}这种方式创建索引。MongoDB会按如下方式组织用户:
- > db.people.ensureIndex({"username":1,"age":-1})
- > db.people.find();
- { "_id" : ObjectId("504cbef68bba9d80c955e974"), "username" : "smith", "age" : 48, "user_id" : 0 }
- { "_id" : ObjectId("504cbf048bba9d80c955e975"), "username" : "smith", "age" : 30, "user_id" : 1 }
- { "_id" : ObjectId("504cbf158bba9d80c955e976"), "username" : "john", "age" : 36, "user_id" : 2 }
- { "_id" : ObjectId("504cbf238bba9d80c955e977"), "username" : "john", "age" : 18, "user_id" : 3 }
- { "_id" : ObjectId("504cbf378bba9d80c955e978"), "username" : "joe", "age" : 36, "user_id" : 4 }
- { "_id" : ObjectId("504cbf488bba9d80c955e979"), "username" : "john", "age" : 7, "user_id" : 5 }
- { "_id" : ObjectId("504cbf588bba9d80c955e97a"), "username" : "simon", "age" : 3, "user_id" : 6 }
- { "_id" : ObjectId("504cbf688bba9d80c955e97b"), "username" : "joe", "age" : 27, "user_id" : 7 }
- { "_id" : ObjectId("504cbf7a8bba9d80c955e97c"), "username" : "jacob", "age" : 17, "user_id" : 8 }
- { "_id" : ObjectId("504cbf8f8bba9d80c955e97d"), "username" : "sally", "age" : 52, "user_id" : 9 }
- { "_id" : ObjectId("504cbf9f8bba9d80c955e97e"), "username" : "simon", "age" : 59, "user_id" : 10 }
在age上建立索引
- > db.people.ensureIndex({"age":1})
- > db.people.find({"age":{"$lt":100,"$gt":0}});
- { "_id" : ObjectId("504cbf9f8bba9d80c955e97e"), "username" : "simon", "age" : 59, "user_id" : 10 }
- { "_id" : ObjectId("504cbf8f8bba9d80c955e97d"), "username" : "sally", "age" : 52, "user_id" : 9 }
- { "_id" : ObjectId("504cbef68bba9d80c955e974"), "username" : "smith", "age" : 48, "user_id" : 0 }
- { "_id" : ObjectId("504cbf158bba9d80c955e976"), "username" : "john", "age" : 36, "user_id" : 2 }
- { "_id" : ObjectId("504cbf378bba9d80c955e978"), "username" : "joe", "age" : 36, "user_id" : 4 }
- { "_id" : ObjectId("504cbf048bba9d80c955e975"), "username" : "smith", "age" : 30, "user_id" : 1 }
- { "_id" : ObjectId("504cbf688bba9d80c955e97b"), "username" : "joe", "age" : 27, "user_id" : 7 }
- { "_id" : ObjectId("504cbf238bba9d80c955e977"), "username" : "john", "age" : 18, "user_id" : 3 }
- { "_id" : ObjectId("504cbf7a8bba9d80c955e97c"), "username" : "jacob", "age" : 17, "user_id" : 8 }
- { "_id" : ObjectId("504cbf488bba9d80c955e979"), "username" : "john", "age" : 7, "user_id" : 5 }
- { "_id" : ObjectId("504cbf588bba9d80c955e97a"), "username" : "simon", "age" : 3, "user_id" : 6 }
- >
重新以{"username":1,"age":-1}这种方式创建索引。
MongoDB会按如下方式组织用户
- > db.people.find({"username":/^j/i,"age":{"$lt":100,"$gt":0}});
- { "_id" : ObjectId("504cbf7a8bba9d80c955e97c"), "username" : "jacob", "age" : 17, "user_id" : 8 }
- { "_id" : ObjectId("504cbf378bba9d80c955e978"), "username" : "joe", "age" : 36, "user_id" : 4 }
- { "_id" : ObjectId("504cbf688bba9d80c955e97b"), "username" : "joe", "age" : 27, "user_id" : 7 }
- { "_id" : ObjectId("504cbf158bba9d80c955e976"), "username" : "john", "age" : 36, "user_id" : 2 }
- { "_id" : ObjectId("504cbf238bba9d80c955e977"), "username" : "john", "age" : 18, "user_id" : 3 }
- { "_id" : ObjectId("504cbf488bba9d80c955e979"), "username" : "john", "age" : 7, "user_id" : 5 }
- >
用户名严格按照字母升序排列,同名的组按照年龄降序排列。这对{"username":1,"age":-1}这样的排序做了优化,但是对{"username":1,"age":1}就不那么有效了。要是想对其优化的话,则需建立{"username":1,"age":1}的索引以便按照年龄升序组织。
对用户名和年龄的索引同样能加快对用户名的查询。一般来说,如果索引包含N个键,则对于前几个键的查询都会有帮助。比如有个索引{"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":"foo","y":"bar"}的时候,已经有了{"y":1,"x":1}的索引,MongoDB会自己找到并利用它。
创建索引的缺点就是每次插入、更新和删除时都会产生额外的开销。只是因为数据库不但需要执行这些操作,还要将这些操作在集合的索引中标记。因此,要极可能少创建索引。每个集合默认的最大索引个数为64个,能够应付绝大多数情况了。
一定不要索引每一个键。这样会导致插入非常慢,还会占用很多空间,并且很可能对查询速度提升不大。仔细考虑到底要做什么样的查询,什么样的索引适合这样的查询,通过explain和hint工具确保服务器使用了也已建立的索引
有些时候,最有效的方法居然是不适用索引。一般来说,要是查询要返回集合中一半以上的结果,用表扫描会比几乎每条文档都查索引要高效一些。所以,查询是否存在某个键,或者检查某个布尔类型的值为真还是为假,真的没有用索引的必要
5.1.1 扩展索引
假设我们有个集合,保存了用户的状态信息。现在想要查询用户和日期,取出某一个用户最近的状态。以我们目前所学,我们会像下面