[关闭]
@52fhy 2015-09-20T15:36:48.000000Z 字数 6891 阅读 369

MongoDB——第四天 索引操作

mongo


好,今天分享下mongodb中关于索引的基本操作,我们日常做开发都避免不了要对程序进行性能优化,而程序的操作无非就是CURD,通常我们又会花费50%的时间在R上面,因为Read操作对用户来说是非常敏感的,处理不好就会被人唾弃。

从算法上来说有5种经典的查找,具体的可以参见我的算法速成系列,这其中就包括我们今天所说的“索引查找”,如果大家对sqlserver比较了解的话,相信索引查找能给我们带来什么样的性能提升吧。

我们首先插入10w数据,上程序说话:

  1. db.person.drop();//删除person集合
  2. //创建10W条数据
  3. for(var i=0; i< 100000; i++){
  4. db.user.insert({"name":"hxj"+i, "age" : i});
  5. }
  6. > db.user.count();
  7. 100000

已经有10W条数据了。

一:性能分析函数(explain)

好了,数据已经插入成功,既然我们要做分析,肯定要有分析的工具,幸好mongodb中给我们提供了一个关键字叫做“explain",那么怎么用呢?

还是看程序,注意,这里的name字段没有建立任何索引,这里我就查询一个“name10000”的姓名。

  1. > db.user.find({"name" : "hxj"+10000});
  2. { "_id" : ObjectId("55935a673cb05382291d31a0"), "name" : "hxj10000", "age" : 10000 }
  3. > db.user.find({"name" : "hxj"+10000}).explain();
  4. {
  5. "queryPlanner" : {
  6. "plannerVersion" : 1,
  7. "namespace" : "test.user",
  8. "indexFilterSet" : false,
  9. "parsedQuery" : {
  10. "name" : {
  11. "$eq" : "hxj10000"
  12. }
  13. },
  14. "winningPlan" : {
  15. "stage" : "COLLSCAN",
  16. "filter" : {
  17. "name" : {
  18. "$eq" : "hxj10000"
  19. }
  20. },
  21. "direction" : "forward"
  22. },
  23. "rejectedPlans" : [ ]
  24. },
  25. "serverInfo" : {
  26. "host" : "YJC-PC",
  27. "port" : 27017,
  28. "version" : "3.0.4",
  29. "gitVersion" : "0481c958daeb2969800511e7475dc66986fa9ed5"
  30. },
  31. "ok" : 1
  32. }

其中:

  1. "stage" : "COLLSCAN",

stageCOLLSCAN,说明没有走索引,走索引的话会显示IXSCAN

基于mongo3.0,和2.x的版本有些地方会不大一样。

旧版的话,会有几个Key可以看下:
cursor: 如果出现的是BasicCursor,就是说这里的查找采用的是“表扫描”,也就是顺序查找,很悲催啊。
nscanned:表示数据库浏览了多少个文档。
n: 最终返回了多少个文档。
millis:总共耗时多少毫秒。

二:建立索引(ensureIndex)

在10w条这么简单的集合中查找一个文档要114毫秒有一点点让人不能接收(2.x版本),好,那么我们该如何优化呢?mongodb中给我们带来了索引查找,看看能不能让我们的查询一飞冲天.....

  1. > db.user.ensureIndex({"name":1});
  2. {
  3. "createdCollectionAutomatically" : false,
  4. "numIndexesBefore" : 1,
  5. "numIndexesAfter" : 2,
  6. "ok" : 1
  7. }
  8. > db.user.find({"name" : "hxj"+10000}).explain();
  9. {
  10. "queryPlanner" : {
  11. "plannerVersion" : 1,
  12. "namespace" : "test.user",
  13. "indexFilterSet" : false,
  14. "parsedQuery" : {
  15. "name" : {
  16. "$eq" : "hxj10000"
  17. }
  18. },
  19. "winningPlan" : {
  20. "stage" : "FETCH",
  21. "inputStage" : {
  22. "stage" : "IXSCAN",
  23. "keyPattern" : {
  24. "name" : 1
  25. },
  26. "indexName" : "name_1",
  27. "isMultiKey" : false,
  28. "direction" : "forward",
  29. "indexBounds" : {
  30. "name" : [
  31. "[\"hxj10000\", \"hxj10000\"]"
  32. ]
  33. }
  34. }
  35. },
  36. "rejectedPlans" : [ ]
  37. },
  38. "serverInfo" : {
  39. "host" : "YJC-PC",
  40. "port" : 27017,
  41. "version" : "3.0.4",
  42. "gitVersion" : "0481c958daeb2969800511e7475dc66986fa9ed5"
  43. },
  44. "ok" : 1
  45. }

这里我们使用了ensureIndex在name上建立了索引。
1:表示按照name进行升序,-1:表示按照name进行降序。

这回stageFETCH

如果是2.x版本,会有这些信息:
cursor: 如果是BtreeCursor,这么牛X,mongodb采用B树的结构来存放索引,索引名为后面的“name_1"。
nscanned:表示数据库浏览了多少个文档。
n: 最终返回了多少个文档。
millis:总共耗时多少毫秒。

通过这个例子相信大家对索引也有了感官方面的认识了吧。

三:唯一索引

和sqlserver一样都可以建立唯一索引,重复的键值自然就不能插入,在mongodb中的使用方法是:

  1. db.user.ensureIndex({"name":1},{"unique":true})
  2. /*删除所有数据,发现删除不了*/
  3. > db.user.remove()
  4. 2015-07-01T11:29:38.579+0800 E QUERY Error: remove needs a query
  5. at Error (<anonymous>)
  6. at DBCollection._parseRemove (src/mongo/shell/collection.js:305:32)
  7. at DBCollection.remove (src/mongo/shell/collection.js:328:23)
  8. at (shell):1:9 at src/mongo/shell/collection.js:305
  9. /*删除所有数据*/
  10. > db.user.remove({})
  11. WriteResult({ "nRemoved" : 100000 })
  12. > db.user.ensureIndex({"name":1}, {"unique": true});
  13. {
  14. "createdCollectionAutomatically" : false,
  15. "numIndexesBefore" : 2,
  16. "errmsg" : "exception: Index with name: name_1 already exists with different options",
  17. "code" : 85,
  18. "ok" : 0
  19. }
  20. > db.user.count()
  21. 0
  22. /*清空集合里数据*/
  23. > db.user.drop()
  24. true
  25. > db.user.ensureIndex({"name":1}, {"unique": true});
  26. {
  27. "createdCollectionAutomatically" : true,
  28. "numIndexesBefore" : 1,
  29. "numIndexesAfter" : 2,
  30. "ok" : 1
  31. }
  32. > db.user.insert({"name":"yjc", "age": 22});
  33. WriteResult({ "nInserted" : 1 })
  34. > db.user.insert({"name":"yjc", "age": 23});
  35. WriteResult({
  36. "nInserted" : 0,
  37. "writeError" : {
  38. "code" : 11000,
  39. "errmsg" : "E11000 duplicate key error index: test.user.$name_1 dup key: { : \"yjc\" }"
  40. }
  41. })

四:组合索引

有时候我们的查询不是单条件的,可能是多条件,比如查找出生在‘1989-3-2’名字叫‘jack’的同学,那么我们可以建立“姓名”和"生日“的联合索引来加速查询。

  1. > db.user.insert({"name" : "hxc", "birthday" : "1989-2-2"});
  2. WriteResult({ "nInserted" : 1 })
  3. > db.user.insert({"name" : "jack", "birthday" : "1989-3-2"});
  4. WriteResult({ "nInserted" : 1 })
  5. > db.user.insert({"name" : "joe", "birthday" : "1989-2-22"});
  6. WriteResult({ "nInserted" : 1 })
  7. > db.user.insert({"name" : "mary", "birthday" : "1989-3-12"});
  8. WriteResult({ "nInserted" : 1 })
  9. > db.user.insert({"name" : "jr", "birthday" : "1989-3-2"});
  10. WriteResult({ "nInserted" : 1 })
  11. > db.user.ensureIndex({"name":1, "birthday":1})
  12. {
  13. "createdCollectionAutomatically" : false,
  14. "numIndexesBefore" : 2,
  15. "numIndexesAfter" : 3,
  16. "ok" : 1
  17. }
  18. > db.user.ensureIndex({"birthday":1, "name":1})
  19. {
  20. "createdCollectionAutomatically" : false,
  21. "numIndexesBefore" : 3,
  22. "numIndexesAfter" : 4,
  23. "ok" : 1
  24. }

看到上面,大家或者也知道name跟birthday的不同,建立的索引也不同,升序和降序的顺序不同都会产生不同的索引,那么我们可以用getindexes来查看下person集合中到底生成了那些索引。

  1. > db.user.getIndexes()
  2. [
  3. {
  4. "v" : 1,
  5. "key" : {
  6. "_id" : 1
  7. },
  8. "name" : "_id_",
  9. "ns" : "test.user"
  10. },
  11. {
  12. "v" : 1,
  13. "unique" : true,
  14. "key" : {
  15. "name" : 1
  16. },
  17. "name" : "name_1",
  18. "ns" : "test.user"
  19. },
  20. {
  21. "v" : 1,
  22. "key" : {
  23. "name" : 1,
  24. "birthday" : 1
  25. },
  26. "name" : "name_1_birthday_1",
  27. "ns" : "test.user"
  28. },
  29. {
  30. "v" : 1,
  31. "key" : {
  32. "birthday" : 1,
  33. "name" : 1
  34. },
  35. "name" : "birthday_1_name_1",
  36. "ns" : "test.user"
  37. }
  38. ]

此时我们肯定很好奇,到底查询优化器会使用哪个查询作为操作:

  1. > db.user.find({"name":"jack", "birthday":"1989-3-2"}).explain();
  2. {
  3. "queryPlanner" : {
  4. "plannerVersion" : 1,
  5. "namespace" : "test.user",
  6. "indexFilterSet" : false,
  7. "parsedQuery" : {
  8. "$and" : [
  9. {
  10. "birthday" : {
  11. "$eq" : "1989-3-2"
  12. }
  13. },
  14. {
  15. "name" : {
  16. "$eq" : "jack"
  17. }
  18. }
  19. ]
  20. },
  21. "winningPlan" : {
  22. "stage" : "KEEP_MUTATIONS",
  23. "inputStage" : {
  24. "stage" : "FETCH",
  25. "filter" : {
  26. "birthday" : {
  27. "$eq" : "1989-3-2"
  28. }
  29. },
  30. "inputStage" : {
  31. "stage" : "IXSCAN",
  32. "keyPattern" : {
  33. "name" : 1
  34. },
  35. "indexName" : "name_1",
  36. "isMultiKey" : false,
  37. "direction" : "forward",
  38. "indexBounds" : {
  39. "name" : [
  40. "[\"jack\", \"jack\"]"
  41. ]
  42. }
  43. }
  44. }
  45. },
  46. "rejectedPlans" : [ ]
  47. },
  48. "serverInfo" : {
  49. "host" : "YJC-PC",
  50. "port" : 27017,
  51. "version" : "3.0.4",
  52. "gitVersion" : "0481c958daeb2969800511e7475dc66986fa9ed5"
  53. },
  54. "ok" : 1
  55. }

看到结果我们要相信查询优化器,它给我们做出的选择往往是最优的,因为我们做查询时,查询优化器会使用我们建立的这些索引来创建查询方案,如果某一个先执行完则其他查询方案被close掉,这种方案会被mongodb保存起来,当然如果非要用自己指定的查询方案,这也是可以的,在mongodb中给我们提供了hint方法让我们可以暴力执行。

  1. > db.user.find({"name":"jack", "birthday":"1989-3-2"}).hint({"birthday":1, "name":1}).explain();
  2. {
  3. "queryPlanner" : {
  4. "plannerVersion" : 1,
  5. "namespace" : "test.user",
  6. "indexFilterSet" : false,
  7. "parsedQuery" : {
  8. "$and" : [
  9. {
  10. "birthday" : {
  11. "$eq" : "1989-3-2"
  12. }
  13. },
  14. {
  15. "name" : {
  16. "$eq" : "jack"
  17. }
  18. }
  19. ]
  20. },
  21. "winningPlan" : {
  22. "stage" : "FETCH",
  23. "inputStage" : {
  24. "stage" : "IXSCAN",
  25. "keyPattern" : {
  26. "birthday" : 1,
  27. "name" : 1
  28. },
  29. "indexName" : "birthday_1_name_1",
  30. "isMultiKey" : false,
  31. "direction" : "forward",
  32. "indexBounds" : {
  33. "birthday" : [
  34. "[\"1989-3-2\", \"1989-3-2\"]"
  35. ],
  36. "name" : [
  37. "[\"jack\", \"jack\"]"
  38. ]
  39. }
  40. }
  41. },
  42. "rejectedPlans" : [ ]
  43. },
  44. "serverInfo" : {
  45. "host" : "YJC-PC",
  46. "port" : 27017,
  47. "version" : "3.0.4",
  48. "gitVersion" : "0481c958daeb2969800511e7475dc66986fa9ed5"
  49. },
  50. "ok" : 1
  51. }

五: 删除索引

可能随着业务需求的变化,原先建立的索引可能没有存在的必要了,可能有的人想说没必要就没必要呗,但是请记住,索引会降低CUD这三种操作的性能,因为这玩意需要实时维护,所以啥问题都要综合考虑一下,这里就把刚才建立的索引清空掉来演示一下:dropIndex的使用。

dropIndex()删除某个索引
dropIndexes()删除全部索引

先查看索引:

  1. > db.user.getIndexes()
  2. [
  3. {
  4. "v" : 1,
  5. "key" : {
  6. "_id" : 1
  7. },
  8. "name" : "_id_",
  9. "ns" : "test.user"
  10. },
  11. {
  12. "v" : 1,
  13. "key" : {
  14. "name" : 1,
  15. "birthday" : 1
  16. },
  17. "name" : "name_1_birthday_1",
  18. "ns" : "test.user"
  19. },
  20. {
  21. "v" : 1,
  22. "key" : {
  23. "name" : 1
  24. },
  25. "name" : "name_1",
  26. "ns" : "test.user"
  27. },
  28. {
  29. "v" : 1,
  30. "key" : {
  31. "name" : 1,
  32. "unique" : true
  33. },
  34. "name" : "name_1_unique_true",
  35. "ns" : "test.user"
  36. }
  37. ]

删除普通索引:

  1. > db.user.dropIndex("name_1");
  2. { "nIndexesWas" : 4, "ok" : 1 }

删除全部索引:

  1. > db.user.dropIndexes();
  2. {
  3. "nIndexesWas" : 3,
  4. "msg" : "non-_id indexes dropped for collection",
  5. "ok" : 1
  6. }

查看还有什么索引

  1. > db.user.getIndexes()
  2. [
  3. {
  4. "v" : 1,
  5. "key" : {
  6. "_id" : 1
  7. },
  8. "name" : "_id_",
  9. "ns" : "test.user"
  10. }
  11. ]

参考:
8天学通MongoDB——第四天 索引操作 - 一线码农 - 博客园
http://www.cnblogs.com/huangxincheng/archive/2012/02/29/2372699.html

添加新批注
在作者公开此批注前,只有你和作者可见。
回复批注