MongoDB慢查询需服务端开启profiling并设slowms阈值,Node.js驱动不自动记录,须手动监听命令事件统计耗时;system.profile需建索引防查询慢;IXSCAN不等于高效,应结合explain分析nReturned与totalDocsExamined。

为什么 slowms 设置了却没日志?
默认 MongoDB 不会记录慢查询,必须显式开启日志级别并配置阈值。Node.js 驱动本身不处理慢查询日志,它依赖 MongoDB 服务端的 slowms 参数和日志级别设置。常见错误是只改了客户端连接参数,却没动 mongod 启动配置或运行时配置。
实操建议:
- 确认 MongoDB 服务端已启用日志:启动时加
--logpath /var/log/mongodb/mongod.log --logappend,或在mongod.conf中设置systemLog项 - 设置慢查询阈值:运行
db.setProfilingLevel(1, { slowms: 100 })(单位毫秒),1表示记录慢查询,2表示记录全部操作 - 检查当前配置:
db.getProfilingStatus()和db.getProfilingLevel(),确保返回值不是0 - 注意:profiling 默认只写入
system.profile集合,不输出到文件日志;如需文件日志,必须配合verbose日志级别(如--verbosity 1)并启用slowOpThresholdMs
如何让 Node.js 应用感知并上报慢查询?
MongoDB 驱动(如 mongodb v4+)本身不暴露慢查询事件,但可通过监听 commandStarted 和 commandSucceeded 事件手动计算耗时,并结合 filter 判断是否超阈值。
实操建议:
- 在创建
MongoClient时启用命令监控:const client = new MongoClient(uri, { monitorCommands: true }); - 监听事件并统计:
client.on('commandStarted', (event) => { event.start = Date.now(); }); client.on('commandSucceeded', (event) => { const duration = Date.now() - event.start; if (duration > 200) { console.warn(`Slow MongoDB command: ${event.commandName}, ${duration}ms`); // 上报到 Sentry / 写入日志文件 / 推送到 Prometheus } }); - 注意:该方式仅覆盖 driver 发起的命令(如
find、insertOne),不包含聚合管道内部子阶段或索引扫描细节 - 避免在监听回调里做阻塞操作(如同步写文件),否则拖慢整个连接池
profile collection 查询太慢怎么办?
开启 profiling 后,慢查询会写入 system.profile,但这个集合默认无索引,随着数据增长,db.system.profile.find() 会越来越慢,甚至影响主库性能。
使用 JSON Schema 验证 JSON 数据,从示例 JSON 生成 schema,并将其转换为 TypeScript 接口、Python 数据类或 Markdown 文档。
实操建议:
- 为
system.profile添加复合索引:db.system.profile.createIndex({ ts: -1, millis: -1 }),加速按时间倒序 + 耗时过滤 - 限制 profiling 数据保留量:定期清理旧数据,例如
db.system.profile.deleteMany({ ts: { $lt: new Date(Date.now() - 7 * 24 * 60 * 60 * 1000) } }) - 生产环境慎用
profilingLevel = 2(全量记录),推荐始终用1+ 合理slowms(如 150–300ms) - 注意:
system.profile是 capped collection,但大小固定(默认 1MB),容易被新日志覆盖;如需长期留存,应主动导出或用变更流捕获后存入业务表
日志里看到 planSummary: IXSCAN 但还是很慢?
IXSCAN 只表示走了索引,不代表高效——可能是索引未覆盖查询字段(导致回表)、索引选择性差、或扫描了大量索引条目。MongoDB 日志中的 nReturned 和 nScannedObjects 才是关键指标。
实操建议:
- 在日志中定位具体慢操作后,用
explain("executionStats")复现:db.collection.find({ status: "pending" }).explain("executionStats") - 重点看:
executionStats.nReturned(返回文档数)、executionStats.totalDocsExamined(扫描文档数)、executionStats.executionTimeMillis - 如果
totalDocsExamined远大于nReturned,说明索引未覆盖或查询条件未命中索引前缀,需要调整索引字段顺序或增加投影 - 避免对
Date字段做范围查询时,把高基数字段(如userId)放在索引末尾——复合索引应按“等值 → 范围 → 排序”原则组织
MongoDB 慢查询日志真正的瓶颈往往不在“怎么记”,而在于“记下来之后怎么快速定位根因”。system.profile 的原始数据、driver 层的耗时钩子、以及 explain 输出里的扫描量,三者必须交叉比对才能下结论。单独看任一环节都容易误判。

















