记一次MongoDB慢查询

项目刚上线,流量突然变大,随之而来的是不断的异常日志,经排查,发现有一处的 Mongo 查询为 9 秒多。

使用 Profiling

设置

db.setProfilingLevel(level, slowms)

level 级别,0 : 关闭不收任何数据,1 : 收集慢查询,默认为 100 ms,2 : 收集所有数据。
slowms 当超过时,收集数据。

因为 profile 会影响到一定的性能,所以推荐 level 为 1,我这边设为 1 500。

返回:
was 当前的 profiling 等级
slowms 记录的毫秒数

确认配置

db.getProfilingStatus()

其他配置方式

  1. 在 MongoDB 启动的时候设置: mongod --profile 1 --slowms 500
  2. 在配置文件中:
    1
    2
    profile = 1
    slowms = 200

重置

profile 默认的大小为 1 MB,如果需要更大的空间,可以自己进行配置。

1
2
3
4
db.setProfilingLevel(0)
db.system.profile.drop()
db.createCollection( "system.profile", { capped: true, size:4000000 } )
db.setProfilingLevel(1)

其他

profile 和 oplog 一样,都是 capped collection,即在一定大小内,会将新的数据覆盖旧的数据。详见

分析

查询

db.system.profile.find().pretty()
查询的过滤条件和普通查询一致,比如:

1
2
db.system.profile.find( { op: { $ne : ‘command‘ } }).pretty()
db.system.profile.find().sort({ millis: {gte: 1000 }}).pretty()

返回结果,具体可以查看 Database Profiler Output

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
{
"op" : "update", //操作,有 command,count,distinct,geoNear 等
"ns" : "test.test1", //操作的表
"query" : {}, //查询
"updateobj" : {}, //在更新操作时会有该字段,超过 50 kb 时,其余会用 ... 代替
"keysExamined" : 9998, //之前为 nscanned 字段,代表数据操作时,扫描过的 key
"locks" : { //锁相关信息
"Global" : { //全局锁
"acquireCount" : { //操作时请求获取锁的次数
"r" : NumberLong(79),
"w" : NumberLong(79)
}
},
"Database" : {
"acquireCount" : {
"w" : NumberLong(79)
}
},
"Collection" : { //集合锁
"acquireCount" : {
"w" : NumberLong(79)
}
}
},
"millis" : 104,
"ts" : ISODate("2017-07-27T14:20:24.117+08:00"),
"client" : "",
"user" : ""
}

注:锁相关资料可以查看 FAQ

优化

  1. 若为查询语句,可以先观察 keysExamined 字段,如果过大,可以适当调整当前集合的索引。
  2. 聚合查询,不知道是业务较复杂,还是当初设计时候未留有较大的空间,较多的使用 aggregation的查询会使用到 $lookup,可以适量通过冗余,以空间换时间不是个坏方法,另外在 pipe 的时候尽量只提取需要的字段。
  3. 负载均衡,读写分离。
  4. 有一个笨方法,在提交代码之前,可以在本地使用 db.xxx.explain(),先分析性能,再进行优化提交。

这篇文章仅仅是基础,在上线之后的更多经验,愿以后可以慢慢分享。