今天发现查询mongo时容易慢查询,现在将整个排查及优化过程,总结记录如下。
Database Profiler介绍
类似于mysql,Profiler可以实现慢查询日志的功能,它把数据收集在system.profile集合里。该集合是一个capped collection(有上限的集合)。Profiler默认是关闭的,可以在一个database或者一个instance上设置开启,设置不同的profiling levels.
具体描述见Database Profiler。
慢查询分析流程
慢查询日志一般作为优化步骤里的第一步。通过慢查询日志,定位每一条语句的查询时间。比如超过了200ms,那么查询超过200ms的语句需要优化。然后它通过 .explain() 解析影响行数是不是过大,所以导致查询语句超过200ms。
所以优化步骤一般就是:
- 用慢查询日志(system.profile)找到超过200ms的语句
- 然后再通过.explain()解析影响行数,分析为什么超过200ms
- 决定是不是需要添加索引
开启慢查询
Profiling Levels
Level | Description |
---|---|
0 | The profiler is off and does not collect any data. This is the default profiler level. |
1 | The profiler collects data for operations that take longer than the value of slowms. |
2 | The profiler collects data for all operations. |
Enable and Configure Database Profiling
方式1 - from the mongo shell
1 | # 查看状态:级别和时间 |
注意:
- 每次设置之后,返回的是之前的状态
- 每次设置仅对当前所在的database有效
方式2 - 在mongo启动时设置
作为命令行参数传入:
1 | mongod --profile 1 --slowms 15 --slowOpSampleRate 0.5 |
还可以在配置文件里设置
具体设置参考profile
关闭Profiling
1 | uhost:PRIMARY> db.setProfilingLevel(0) |
Profiling的效率问题
这是我能搜到的说法,但是具体原理和数据我还没找到,官网也没看到介绍。
从其他地方复制过来的说法,待论证。”Profiling 功能肯定是会影响效率的,但是不太严重,原因是他使用的是system.profile 来记录,而system.profile 是一个capped collection 这种collection 在操作上有一些限制和特点,但是效率更高“
慢查询结果分析
各项参数含义
这里用的MongoDB版本是2.6, 具体可以参考 https://docs.mongodb.com/v2.6/reference/database-profiler/
1 | uhost:PRIMARY> db.system.profile.find({"ns" : "uhost.DiskAttachInfo","ts":{$gt:new ISODate("2019-07-23T10:11:00Z")}}).pretty() |
分析
如果发现 millis 值比较大,那么就需要作优化。
1 如果nscanned数很大,或者接近记录总数(文档数),那么可能没有用到索引查询,而是全表扫描。
2 如果nscanned值高于nreturned的值,说明数据库为了找到目标文档扫描了很多文档。这时可以考虑创建索引来提高效率。
system.profile补充(这部分待验证)
‘type’的返回参数说明:
- COLLSCAN #全表扫描
- IXSCAN #索引扫描
- FETCH #根据索引去检索指定document
- SHARD_MERGE #将各个分片返回数据进行merge
- SORT #表明在内存中进行了排序(与老版本的scanAndOrder:true一致)
- LIMIT #使用limit限制返回数
- SKIP #使用skip进行跳过
- IDHACK #针对_id进行查询
- SHARDING_FILTER #通过mongos对分片数据进行查询
- COUNT #利用db.coll.explain().count()之类进行count运算
- COUNTSCAN #count不使用Index进行count时的stage返回
- COUNT_SCAN #count使用了Index进行count时的stage返回
- SUBPLA #未使用到索引的$or查询的stage返回
- TEXT #使用全文索引进行查询时候的stage返回
- PROJECTION #限定返回字段时候stage的返回
对于普通查询,我们最希望看到的组合有这些:
- Fetch+IDHACK
- Fetch+ixscan
- Limit+(Fetch+ixscan)
- PROJECTION+ixscan
- SHARDING_FILTER+ixscan
不希望看到包含如下的type:
- COLLSCAN(全表扫)
- SORT(使用sort但是无index)
- 不合理的SKIP,SUBPLA(未用到index的$or)
对于count查询,希望看到的有:COUNT_SCAN
不希望看到的有:COUNTSCAN
常见的慢日志(system.profile)查询语句
返回最近的10条记录:
1 | db.system.profile.find().limit(10).sort( { ts : -1 } ).pretty() |
返回所有的操作,除command类型的
1 | db.system.profile.find( { op: { $ne : ‘command‘ } }).pretty() |
返回特定集合
1 | db.system.profile.find( { ns: "uhost.DiskAttachInfo"} ).pretty() |
To return operations slower than 5 milliseconds, run a query similar to the following:
1 | db.system.profile.find( { millis : { $gt : 5 } } ).pretty() |
To return information from a certain time range, run a query similar to the following:
1 | db.system.profile.find({ |
The following example looks at the time range, suppresses the user field from the output to make it easier to read, and sorts the results by how long each operation took to run:
1 | db.system.profile.find({ |
Show the Five Most Recent Events
On a database that has profiling enabled, the show profile helper in the mongo shell displays the 5 most recent operations that took at least 1 millisecond to execute. Issue show profile from the mongo shell, as follows:
1 | show profile |
explain分析
1 | uhost:PRIMARY> db.getCollection('DiskAttachInfo').find({"backend_types":{$in:["ssd"]}}).explain() |
添加索引
在以上的分析中,”nscanned” 过大,且”type” : “COLLSCAN”为整个集合扫描,”indexOnly” : false表示没有用到索引。考虑创建一个索引来改善这一点。
查看当前索引,在backend_type上并没有索引。
1 | uhost:PRIMARY> db.getCollection('DiskAttachInfo').getIndexes() |
添加index
1 | uhost:PRIMARY> db.DiskAttachInfo.createIndex({"backend_type":1},{background: true, safe: null}) |
添加索引后测试效果
1 | uhost:PRIMARY> db.getCollection('DiskAttachInfo').find({"backend_type":{$in:["ssd"]}}).explain() |
“nscanned” 由原来的52w降低到9w多。
时间(”millis” )由828ms降低到169ms。