排查MongoDB CPU使用率高
在使用云数据库MongoDB的时候您可能会遇到MongoDB CPU使用率很高或者CPU使用率接近100%的问题,从而导致数据读写处理异常缓慢,影响正常业务。本文主要帮助您从应用的角度排查MongoDB CPU使用率高的问题。
分析MongoDB数据库正在执行的请求
- 通过Mongo Shell连接实例。
执行db.currentOp()命令,查看数据库当前正在执行的操作。 - 该命令的输出示例如下。
{ "desc" : "conn632530", "threadId" : "140298196924160", "connectionId" : 632530, "client" : "11.192.159.236:57052", "active" : true, "opid" : 1008837885, "secs_running" : 0, "microsecs_running" : NumberLong(70), "op" : "update", "ns" : "mygame.players", "query" : { "uid" : NumberLong(31577677) }, "numYields" : 0, "locks" : { "Global" : "w", "Database" : "w", "Collection" : "w" }, .... },
您需要重点关注以下几个字段。
字段 | 返回值说明 |
---|---|
client | 该请求是由哪个客户端发起的。 |
opid | 操作的唯一标识符。(如果有需要,可以通过db.killOp(opid)直接终止该操作。) |
secs_running | 表示该操作已经执行的时间,单位为秒。(如果该字段返回的值特别大,需要查看请求是否合理。) |
microsecs_running | 表示该操作已经执行的时间,单位为微秒。(如果该字段返回的值特别大,需要查看请求是否合理。) |
ns | 该操作目标集合。 |
op | 表示操作的类型。通常是查询、插入、更新、删除中的一种。 |
locks | 跟锁相关的信息。 |
通过 db.currentOp() 查看正在执行的操作,分析是否有不正常耗时的请求正在执行。例如您的业务平时CPU使用率不高,运维管理人员连到MongoDB数据库执行了一些需要全表扫描的操作导致CPU使用率非常高,业务响应缓慢,此时需要重点关注执行时间非常耗时的操作。
说明:如果发现有异常的请求,您可以找到该请求对应的opid,执行db.killOp(opid)终止该请求。 |
如果您的应用刚刚上线,MongoDB实例的CPU使用率马上处于持续很高的状态,执行db.currentOp(),在输出结果中未发现异常请求,您可参见下述小节分析数据库慢请求。
分析MongoDB数据库的慢请求
云数据库MongoDB默认开启了慢请求Profiling ,系统自动地将请求时间超过100ms的执行情况记录到对应数据库下的system.profile集合里。
- 通过Mongo Shell连接实例。
通过use
命令进入指定数据库。 use mongodbtest
执行如下命令,查看该数据下的慢请求日志。
db.system.profile.find().pretty()
分析慢请求日志,查找引起MongoDB CPU使用率升高的原因。
以下为某个慢请求日志示例,可查看到该请求进行了全表扫描,扫描了11000000个文档,没有通过索引进行查询。{ "op" : "query", "ns" : "123.testCollection", "command" : { "find" : "testCollection", "filter" : { "name" : "zhangsan" }, "$db" : "123" }, "keysExamined" : 0, "docsExamined" : 11000000, "cursorExhausted" : true, "numYield" : 85977, "nreturned" : 0, "locks" : { "Global" : { "acquireCount" : { "r" : NumberLong(85978) } }, "Database" : { "acquireCount" : { "r" : NumberLong(85978) } }, "Collection" : { "acquireCount" : { "r" : NumberLong(85978) } } }, "responseLength" : 232, "protocol" : "op_command", "millis" : 19428, "planSummary" : "COLLSCAN", "execStats" : { "stage" : "COLLSCAN", "filter" : { "name" : { "$eq" : "zhangsan" } }, "nReturned" : 0, "executionTimeMillisEstimate" : 18233, "works" : 11000002, "advanced" : 0, "needTime" : 11000001, "needYield" : 0, "saveState" : 85977, "restoreState" : 85977, "isEOF" : 1, "invalidates" : 0, "direction" : "forward", ....in" } ], "user" : "root@admin" }
通常在慢请求日志中,您需要重点关注以下几点。
1.全表扫描(关键字: COLLSCAN、 docsExamined )
全集合(表)扫描COLLSCAN 。 当一个操作请求(如查询、更新、删除等)需要全表扫描时,将非常占用CPU资源。在查看慢请求日志时发现COLLSCAN关键字,很可能是这些查询占用了CPU资源.
说明:如果这种请求比较频繁,建议对查询的字段建立索引的方式来优化。 |
通过查看docsExamined的值,可以查看到一个查询扫描了多少文档。该值越大,请求所占用的CPU开销越大。
2.不合理的索引(关键字: IXSCAN、keysExamined )
说明: 1. 索引不是越多越好,索引过多会影响写入、更新的性能。 2. 如果您的应用偏向于写操作,索引可能会影响性能。 |
通过查看keysExamined字段,可以查看到一个使用了索引的查询,扫描了多少条索引。该值越大,CPU开销越大。
如果索引建立的不太合理,或者是匹配的结果很多。这样即使使用索引,请求开销也不会优化很多,执行的速度也会很慢。
如下所示,假设某个集合的数据,x字段取值的重复率很高(假设只有1、2),而y字段取值的重复率很低。
{ x: 1, y: 1 }
{ x: 1, y: 2 }
{ x: 1, y: 3 }
......
{ x: 1, y: 100000}
{ x: 2, y: 1 }
{ x: 2, y: 2 }
{ x: 2, y: 3 }
......
{ x: 1, y: 100000}
要实现 {x: 1, y: 2} 这样的查询。
db.createIndex( {x: 1} ) 效果不好,因为x相同取值太多
db.createIndex( {x: 1, y: 1} ) 效果不好,因为x相同取值太多
db.createIndex( {y: 1 } ) 效果好,因为y相同取值很少
db.createIndex( {y: 1, x: 1 } ) 效果好,因为y相同取值少
关于{y: 1} 与 {y: 1, x: 1} 的区别,可参见复合索引官方文档。
3.大量数据排序(关键字: SORT、hasSortStage )
当查询请求里包含排序的时候, system.profile 集合里的hasSortStage字段会为 true 。如果排序无法通过索引满足,MongoDB会在查询结果中进行排序。而排序这个动作将非常消耗CPU资源,这种情况需要对经常排序的字段建立索引的方式进行优化。
说明:当您在system.profile集合里发现SORT关键字时,可以考虑通过索引来优化排序。 |
其他还有诸如建立索引、aggregation(遍历、查询、更新、排序等动作的组合) 等操作也可能非常耗CPU资源,但本质上也是上述几种场景。更多profiling的设置请参见profiling官方文档。