跳到主要内容

慢查询

2024年04月19日
柏拉文
越努力,越幸运

一、认识


本文介绍了较为全面的 MongoDB 慢查询分析和优化方案, 从慢查询诊断、原因定位、索引与查询优化、数据模型调整到硬件和集群层面的优化,帮助你系统地解决性能瓶颈问题。

二、慢查询分析


2.1 MongoDB Profiler

利用 MongoDB 内置的 Profiler 记录慢查询日志,通过查询 system.profile 集合或者直接分析 mongod 日志获取详细信息。具体操作如下:

  1. 开启慢查询日志: 在 MongoDB 配置文件或启动参数中设置 slowms(默认100ms),记录执行时间超过此值的查询。Profiler 可以帮助捕捉运行中的查询,确定哪些操作耗时较长。可以通过设置不同的级别: 级别 0, 关闭 profiler; 级别 1, 仅捕获慢查询; 级别 2, 捕获所有操作(仅在测试环境使用,生产环境慎用)

    // 开启 profiler,只记录超过100ms的查询
    db.setProfilingLevel(1, { slowms: 100 });
  2. 查看慢查询日志: 慢查询信息会写入 mongod 日志中,也可以通过查询 system.profile 集合查看

    db.system.profile.find({ millis: { $gt: 100 } }).sort({ millis: -1 }).limit(10).pretty();

2.2 MongoDb explain

在查询前加上 .explain("executionStats") 可以详细显示查询的执行计划、扫描的文档数、使用的索引等信息:

db.collection.find({ field: value }).explain("executionStats");
  1. winningPlan: 最终使用的执行计划类型(如 COLLSCAN 表示全表扫描,IXSCAN 表示索引扫描)

  2. totalKeysExaminedtotalDocsExamined: 二者差距较大提示索引命中率不佳

  3. executionTimeMillis: 实际耗时

2.3 MongoDB Atlas Performance Advisor

三、慢查询优化


3.1 索引优化

创建合适的单字段/复合索引, 尽量让索引覆盖查询(即查询字段和返回字段均在索引中), 避免回表查找。利用 db.collection.getIndexes() 检查已有索引,定期清理冗余索引,确保最佳匹配。

3.2 查询优化

一、重构查询条件: 避免使用无法利用索引的操作符(如正则表达式前置通配符、$where),改为精确查询。

二、限制返回字段: 使用 projection 限制返回的字段,减少网络传输和内存占用

3.3 聚合优化

聚合管道 Aggregation Pipeline 优化: 优化的目标是降低管道中数据传输的数量, 移动 $match 以减少不必要的数据处理, 合并可以合并的阶段, 让索引尽可能早地生效。虽然, 我们 MongoDB 内部已经做了许多优化工作, 我们可以基于 MongoDB 内部的优化策略, 我们在代码中就就已经将优化做到极致, 这样我们不仅可以节省 MongoDB 进一步的优化工作, 也提高了我们的代码质量。可以通过 db.collection.aggregate() 方法中添加 explain 选项, 查看优化后的管道。 我们可以在阶段顺序上, $match 应尽早执行,减少后续阶段的数据量, $group 尽量靠后,避免不必要的计算, $project 一般在管道的最后,控制返回的字段; 合并可合并的阶段; 另外, 我们可以增加索引, 确保 $match$lookup 关联字段有索引,否则会导致全表扫描; 我们可以使用 $facet 来实现并行统计, 提高查询效率。在 MongoDB 中,$facet 是聚合管道的一个阶段,它允许你在同一个聚合查询中并行运行多个子管道, 可以并行处理, 每个子管道可以独立处理相同的一组输入文档,并生成各自的输出。这样你就可以一次性获得多种不同角度的聚合结果,而无需多次查询。 注意: $facet 在管道中的第一个阶段, 不会使用索引, 所以, 不要将 $facet 放到第一阶段。