mongodb性能调优初探

> show dbs
admin  0.000GB
local  0.000GB
yang   0.615GB
> use yang
switched to db yang
> show collections
system.profile
users
> db.users.find().count()
10000000

这里选取一千万条数据来说明问题,这些数据都是没有索引的,首先我们要做的是找出慢查询。
1.开启慢查询分析器
db.setProfilingLevel(1,300)
第一个参数“1”表示记录慢查询,还可以选择“0”或者“2”,分别代表不记录数据和记录所有读写操作,我们一般会设置成“1”,第二个参数代表慢查询阈值,只有查询执行时间超过300ms才会记录。
2.查询所记录的慢查询
监控结果保存在一个特殊的盖子集合system.profile里,这个集合分配了128kb的空间,要确保监控分析数据不会消耗太多的系统性资源;盖子集合维护了自然的插入顺序,可以使用$natural操作符进行排序,
> db.system.profile.find().sort({'$natural':-1}).limit(5).pretty()会打印出最耗时的5条查询,在这里我们选取其中的一条来看:

{
        "op" : "command",
        "ns" : "yang.users",
        "command" : {
                "explain" : {
                        "find" : "users",        //这里是所查询的集合
                       //重点在这里,查询条件是 "username" = "dede"
                        "filter" : {
                                "username" : "dede"
                        }
                },
                "verbosity" : "allPlansExecution"
        },
        "numYield" : 78375,
        "locks" : {
                "Global" : {
                        "acquireCount" : {
                                "r" : NumberLong(156752)
                        }
                },
                "Database" : {
                        "acquireCount" : {
                                "r" : NumberLong(78376)
                        }
                },
                "Collection" : {
                        "acquireCount" : {
                                "r" : NumberLong(78376)
                        }
                }
        },
        "responseLength" : 848,
        "protocol" : "op_command",
        "millis" : 6800,
        "ts" : ISODate("2018-02-05T10:35:07.576Z"),
        "client" : "127.0.0.1",
        "appName" : "MongoDB Shell",
        "allUsers" : [ ],
        "user" : ""
}

3.上面的还不够直观,我们现在构造这个查询并用explain来分析具体慢在了那里:
> db.users.find({"username":"dede"}).explain('executionStats')
explain的入参可选值为:
"queryPlanner" 是默认值,表示仅仅展示执行计划信息;
"executionStats" 表示展示执行计划信息同时展示被选中的执行计划的执行情况信息;
 "allPlansExecution" 表示展示执行计划信息,并展示被选中的执行计划的执行情况信息,还展示备选的执行计划的执行情况信息;
我们一般会选用executionStats,打印出来的内容如下:

> db.users.find({"username":"dede"}).explain('executionStats')
{
        "queryPlanner" : {
                "plannerVersion" : 1,
                "namespace" : "yang.users",
                "indexFilterSet" : false,
                "parsedQuery" : {
                        "username" : {
                                "$eq" : "dede"
                        }
                },
         //执行计划会有多种,这里列出的是胜出的执行计划
                "winningPlan" : {
         //重点看下面的stage,collscan代表全表扫描,如果命中索引这里应该是ixscan,index scan
                        "stage" : "COLLSCAN",
                        "filter" : {
                                "username" : {
                                        "$eq" : "dede"
                                }
                        },
                        "direction" : "forward"
                },
         //下面列举的是没有使用的执行计划
                "rejectedPlans" : [ ]
        },
        "executionStats" : {
                "executionSuccess" : true,
                "nReturned" : 1428802,
                "executionTimeMillis" : 7080,
                "totalKeysExamined" : 0,
                "totalDocsExamined" : 10000000,
                "executionStages" : {
                        "stage" : "COLLSCAN",
                        "filter" : {
                                "username" : {
                                        "$eq" : "dede"
                                }
                        },
                        "nReturned" : 1428802,//查询所返回的条数
                        "executionTimeMillisEstimate" : 6633,//执行总时间
                        "works" : 10000002,
                        "advanced" : 1428802,
                        "needTime" : 8571199,
                        "needYield" : 0,
                        "saveState" : 78368,
                        "restoreState" : 78368,
                        "isEOF" : 1,
                        "invalidates" : 0,
                        "direction" : "forward",
                        "docsExamined" : 10000000//总共扫描了一千万条数据
                }
        },
        "serverInfo" : {
                "host" : "localhost.localdomain",
                "port" : 27022,
                "version" : "3.4.10",
                "gitVersion" : "078f28920cb24de0dd479b5ea6c66c644f6326e9"
        },
        "ok" : 1  
}

讲上面的结果简化如下:

queryPlanner(执行计划描述)
        winningPlan(被选中的执行计划) 
                stage(可选项:COLLSCAN 没有走索引;IXSCAN使用了索引) 
        rejectedPlans(候选的执行计划) 
 executionStats(执行情况描述)
        nReturned (返回的文档个数) 
        executionTimeMillis(执行时间ms) 
        totalKeysExamined (检查的索引键值个数) 
        totalDocsExamined (检查的文档个数)

我们最终的优化目标

1.根据需求建立索引
2.每个查询都要使用索引以提高查询效率, winningPlan. stage 必须为IXSCAN ;
3.追求totalDocsExamined = nReturned
属性名 类型 说明
background boolean 是否后台构建索引,在生产环境中,如果数据量太大,构建索引可能会消耗很长时间,为了不影响业务,可以加上此参数,后台运行同时还会为其他读写操作让路
unique boolean 是否为唯一索引
name string 索引名字
sparse boolean 是否为稀疏索引,索引仅引用具有指定字段的文档。

那么mongodb的索引又是怎么建立的呢?
首先,mongodb的索引分为单建索引db.users. createIndex({age:-1}); 、复合索引db.users. createIndex({username:1,age:-1,country:1});、多键索引(在数组的属性上建立索引)db.users. createIndex({favorites.city:1})
创建索引的语法如下:
db.collection.createIndex(keys, options)
语法中 Key 值为要创建的索引字段,1为指定按升序创建索引,如果你想按降序来创建索引指定为-1,也可以指定为hashed(哈希索引)。
语法中options为索引的属性,属性说明见下表;

属性名 类型 说明
background boolean 是否后台构建索引,在生产环境中,如果数据量太大,构建索引可能会消耗很长时间,为了不影响业务,可以加上此参数,后台运行同时还会为其他读写操作让路
unique boolean 是否为唯一索引
name string 索引名字
sparse boolean 是否为稀疏索引,索引仅引用具有指定字段的文档。

举例如下:
单键唯一索引:db.users. createIndex({username :1},{unique:true});
单键唯一稀疏索引:db.users. createIndex({username :1},{unique:true,sparse:true});
复合唯一稀疏索引:db.users. createIndex({username:1,age:-1},{unique:true,sparse:true});
创建哈希索引并后台运行:db.users. createIndex({username :'hashed'},{background:true});
删除索引
根据索引名字删除某一个指定索引:db.users.dropIndex("username_1");
删除某集合上所有索引:db.users.dropIndexs();
重建某集合上所有索引:db.users.reIndex();
查询集合上所有索引:db.users.getIndexes();
既然已经知道了如何创建索引,那么我们就给users表的username字段创建索引
> db.users.createIndex({"username":1},{"name":"username_1","sparse":true,"background":true});
看看有没有创建成功

> db.users.getIndexes()
[
        {
                "v" : 2,
                "key" : {
                        "_id" : 1
                },
                "name" : "_id_",
                "ns" : "yang.users"
        },
        {
                "v" : 2,
                "key" : {
                        "username" : 1
                },
                "name" : "username_1",
                "ns" : "yang.users",
                "sparse" : true,
                "background" : true
        }
]

可以看到索引已经创建成功,索引得名字就是username_1,我们再查看一下执行计划

> db.users.find({"username":"dede"}).explain("executionStats")
{
        "queryPlanner" : {
                "plannerVersion" : 1,
                "namespace" : "yang.users",
                "indexFilterSet" : false,
                "parsedQuery" : {
                        "username" : {
                                "$eq" : "dede"
                        }
                },
                "winningPlan" : {
                        "stage" : "FETCH",
                        "inputStage" : {
                                "stage" : "IXSCAN",
                                "keyPattern" : {
                                        "username" : 1
                                },
                                "indexName" : "username_1",
                                "isMultiKey" : false,
                                "multiKeyPaths" : {
                                        "username" : [ ]
                                },
                                "isUnique" : false,
                                "isSparse" : true,
                                "isPartial" : false,
                                "indexVersion" : 2,
                                "direction" : "forward",
                                "indexBounds" : {
                                        "username" : [
                                                "[\"dede\", \"dede\"]"
                                        ]
                                }
                        }
                },
                "rejectedPlans" : [ ]
        },
        "executionStats" : {
                "executionSuccess" : true,
                "nReturned" : 1428802,
                "executionTimeMillis" : 4399,
                "totalKeysExamined" : 1428802,
                "totalDocsExamined" : 1428802,
                "executionStages" : {
                        "stage" : "FETCH",
                        "nReturned" : 1428802,
                        "executionTimeMillisEstimate" : 4002,
                        "works" : 1428803,
                        "advanced" : 1428802,
                        "needTime" : 0,
                        "needYield" : 0,
                        "saveState" : 11308,
                        "restoreState" : 11308,
                        "isEOF" : 1,
                        "invalidates" : 0,
                        "docsExamined" : 1428802,
                        "alreadyHasObj" : 0,
                        "inputStage" : {
                                "stage" : "IXSCAN",
                                "nReturned" : 1428802,
                                "executionTimeMillisEstimate" : 765,
                                "works" : 1428803,
                                "advanced" : 1428802,
                                "needTime" : 0,
                                "needYield" : 0,
                                "saveState" : 11308,
                                "restoreState" : 11308,
                                "isEOF" : 1,
                                "invalidates" : 0,
                                "keyPattern" : {
                                        "username" : 1
                                },
                                "indexName" : "username_1",
                                "isMultiKey" : false,
                                "multiKeyPaths" : {
                                        "username" : [ ]
                                },
                                "isUnique" : false,
                                "isSparse" : true,
                                "isPartial" : false,
                                "indexVersion" : 2,
                                "direction" : "forward",
                                "indexBounds" : {
                                        "username" : [
                                                "[\"dede\", \"dede\"]"
                                        ]
                                },
                                "keysExamined" : 1428802,
                                "seeks" : 1,
                                "dupsTested" : 0,
                                "dupsDropped" : 0,
                                "seenInvalidated" : 0
                        }
                }
        },
        "serverInfo" : {
                "host" : "localhost.localdomain",
                "port" : 27022,
                "version" : "3.4.10",
                "gitVersion" : "078f28920cb24de0dd479b5ea6c66c644f6326e9"
        },
        "ok" : 1
}

我们可以看到,查询时间从原来的6s变成了765ms,并且执行计划中的stage:IXSCAN,再看看"docsExamined" : 1428802,"nReturned" : 1428802,,两者相等,至此我们完成了mongodb的初步调优,这里只是列举的非常简单的场景,具体的调优还是要看各位的实际情况去分析。

最后编辑于
©著作权归作者所有,转载或内容合作请联系作者
  • 序言:七十年代末,一起剥皮案震惊了整个滨河市,随后出现的几起案子,更是在滨河造成了极大的恐慌,老刑警刘岩,带你破解...
    沈念sama阅读 218,546评论 6 507
  • 序言:滨河连续发生了三起死亡事件,死亡现场离奇诡异,居然都是意外死亡,警方通过查阅死者的电脑和手机,发现死者居然都...
    沈念sama阅读 93,224评论 3 395
  • 文/潘晓璐 我一进店门,熙熙楼的掌柜王于贵愁眉苦脸地迎上来,“玉大人,你说我怎么就摊上这事。” “怎么了?”我有些...
    开封第一讲书人阅读 164,911评论 0 354
  • 文/不坏的土叔 我叫张陵,是天一观的道长。 经常有香客问我,道长,这世上最难降的妖魔是什么? 我笑而不...
    开封第一讲书人阅读 58,737评论 1 294
  • 正文 为了忘掉前任,我火速办了婚礼,结果婚礼上,老公的妹妹穿的比我还像新娘。我一直安慰自己,他们只是感情好,可当我...
    茶点故事阅读 67,753评论 6 392
  • 文/花漫 我一把揭开白布。 她就那样静静地躺着,像睡着了一般。 火红的嫁衣衬着肌肤如雪。 梳的纹丝不乱的头发上,一...
    开封第一讲书人阅读 51,598评论 1 305
  • 那天,我揣着相机与录音,去河边找鬼。 笑死,一个胖子当着我的面吹牛,可吹牛的内容都是我干的。 我是一名探鬼主播,决...
    沈念sama阅读 40,338评论 3 418
  • 文/苍兰香墨 我猛地睁开眼,长吁一口气:“原来是场噩梦啊……” “哼!你这毒妇竟也来了?” 一声冷哼从身侧响起,我...
    开封第一讲书人阅读 39,249评论 0 276
  • 序言:老挝万荣一对情侣失踪,失踪者是张志新(化名)和其女友刘颖,没想到半个月后,有当地人在树林里发现了一具尸体,经...
    沈念sama阅读 45,696评论 1 314
  • 正文 独居荒郊野岭守林人离奇死亡,尸身上长有42处带血的脓包…… 初始之章·张勋 以下内容为张勋视角 年9月15日...
    茶点故事阅读 37,888评论 3 336
  • 正文 我和宋清朗相恋三年,在试婚纱的时候发现自己被绿了。 大学时的朋友给我发了我未婚夫和他白月光在一起吃饭的照片。...
    茶点故事阅读 40,013评论 1 348
  • 序言:一个原本活蹦乱跳的男人离奇死亡,死状恐怖,灵堂内的尸体忽然破棺而出,到底是诈尸还是另有隐情,我是刑警宁泽,带...
    沈念sama阅读 35,731评论 5 346
  • 正文 年R本政府宣布,位于F岛的核电站,受9级特大地震影响,放射性物质发生泄漏。R本人自食恶果不足惜,却给世界环境...
    茶点故事阅读 41,348评论 3 330
  • 文/蒙蒙 一、第九天 我趴在偏房一处隐蔽的房顶上张望。 院中可真热闹,春花似锦、人声如沸。这庄子的主人今日做“春日...
    开封第一讲书人阅读 31,929评论 0 22
  • 文/苍兰香墨 我抬头看了看天上的太阳。三九已至,却和暖如春,着一层夹袄步出监牢的瞬间,已是汗流浃背。 一阵脚步声响...
    开封第一讲书人阅读 33,048评论 1 270
  • 我被黑心中介骗来泰国打工, 没想到刚下飞机就差点儿被人妖公主榨干…… 1. 我叫王不留,地道东北人。 一个月前我还...
    沈念sama阅读 48,203评论 3 370
  • 正文 我出身青楼,却偏偏与公主长得像,于是被迫代替她去往敌国和亲。 传闻我的和亲对象是个残疾皇子,可洞房花烛夜当晚...
    茶点故事阅读 44,960评论 2 355

推荐阅读更多精彩内容

  • Spring Cloud为开发人员提供了快速构建分布式系统中一些常见模式的工具(例如配置管理,服务发现,断路器,智...
    卡卡罗2017阅读 134,657评论 18 139
  • 学习《MongoDB 权威指南·第2版》的笔记,结合 MongoDB 官方最新文档(v3.6),简单记录一些概念、...
    小鱼爱小虾阅读 6,068评论 0 5
  • 目录 查询操作 集合查询方法 find() 查询内嵌文档 查询操作符(内含 数组查询) "$gt" 、"$gte"...
    彩虹之梦阅读 1,023评论 0 1
  • 睡醒找何必带我过了突变,看完了ig的比赛后安利大哥来打合作模式,把沃拉尊升到5级了。菜如我们也是困难随便撸了!晚饭...
    AJI米阅读 125评论 0 0
  • 历经人生沧桑时,或许有种失落感,或许感到形单影只,这时,总会有一种朋友,无须形影相随,无须感天动地,无须多言,便心...
    蒋书丹阅读 192评论 0 0