MongoDB 查询分析

MongoDB 查询分析(Query Analysis)完全指南

目标找出慢查询 → 优化索引 → 提升性能 → 避免事故


一、查询分析核心流程(5 步法)

graph TD
    A[发现慢查询] --> B[查看执行计划 explain()]
    B --> C{是否命中索引?}
    C -->|否| D[创建/优化索引]
    C -->|是| E[检查扫描文档数]
    E --> F{totalDocsExamined ≈ nReturned?}
    F -->|否| G[优化查询/投影]
    F -->|是| H[检查排序/分页]
    H --> I{是否内存排序?}
    I -->|是| J[添加排序索引]
    I -->|否| K[性能已最优]

二、关键工具与命令

工具用途命令
explain()执行计划分析db.collection.find().explain("executionStats")
system.profile慢查询日志db.system.profile.find()
currentOp()实时运行查询db.currentOp()
serverStatus()服务器状态db.serverStatus()
Atlas / PMM可视化监控图形界面

三、explain() 输出详解(重点字段)

db.users.find({ age: 30 }).explain("executionStats")

1. queryPlanner.winningPlan

字段含义
stage: "COLLSCAN"全表扫描 → 危险
stage: "IXSCAN"命中索引 → 好
indexName使用的索引名

2. executionStats

字段含义阈值
totalDocsExamined扫描文档数应接近 nReturned
totalKeysExamined扫描索引键数越少越好
nReturned返回文档数查询结果
executionTimeMillis执行时间> 100ms 需关注

3. 关键判断

条件结论
totalDocsExamined >> nReturned索引无效
stage: "COLLSCAN"无索引
hasSortStage: true内存排序 → 慢
PROJECTION_COVERED覆盖索引 → 极快

四、慢查询日志(Profiler)

1. 开启慢查询记录

// 记录 > 100ms 的查询
db.setProfilingLevel(1, { slowms: 100 })

// 查看状态
db.getProfilingStatus()

2. 查询慢查询日志

// 最近 10 条慢查询
db.system.profile.find()
  .sort({ ts: -1 })
  .limit(10)
  .pretty()

输出示例

{
  "op": "query",
  "ns": "mydb.users",
  "query": { "age": 30 },
  "keysExamined": 0,
  "docsExamined": 1000000,
  "nreturned": 1,
  "millis": 850  // 850ms!
}

五、实战案例分析

案例 1:全表扫描(COLLSCAN

db.orders.find({ status: "pending" }).explain()
{
  "stage": "COLLSCAN",
  "totalDocsExamined": 5000000
}

问题:无索引
解决

db.orders.createIndex({ status: 1 })

案例 2:索引存在但未覆盖

db.users.find(
  { age: { $gte: 25 } },
  { name: 1, email: 1, _id: 0 }
).explain()
{
  "totalDocsExamined": 100000,
  "totalKeysExamined": 100000
}

问题:回表
解决:创建覆盖索引

db.users.createIndex({ age: 1, name: 1, email: 1 })

案例 3:内存排序(SORT 阶段)

db.users.find().sort({ createdAt: -1 }).limit(10).explain()
{
  "stage": "SORT",
  "sortStage": { ... },
  "totalDocsExamined": 1000000
}

问题:内存排序 → OOM 风险
解决

db.users.createIndex({ createdAt: -1 })

六、索引优化黄金法则(ESR)

Equality → Sort → Range

// 查询:status="active" + 按 createdAt 降序 + age > 18
db.logs.createIndex({
  status: 1,        // 等值
  createdAt: -1,    // 排序
  age: 1            // 范围
})

七、实时监控:db.currentOp()

// 查看运行 > 5 秒的查询
db.currentOp({
  "secs_running": { $gt: 5 }
})

杀慢查询

db.killOp(<opId>)

八、Node.js + Mongoose 分析

// 开启 Mongoose 调试
mongoose.set('debug', true);

// 或手动 explain
const result = await User.find({ age: 30 })
  .select('name email')
  .explain('executionStats');

九、生产级监控方案

工具功能
MongoDB Atlas Performance Advisor自动推荐索引
Percona PMMGrafana 仪表盘
Prometheus + mongodb_exporter指标采集
Ops Manager企业级监控

十、查询分析 Checklist

检查项命令
是否命中索引?IXSCAN
扫描文档数?totalDocsExamined
是否回表?totalDocsExamined > nReturned
是否内存排序?SORT 阶段
是否覆盖索引?PROJECTION_COVERED
慢查询记录?db.system.profile

十一、终极优化案例

// 原始慢查询(850ms)
db.orders.find({
  status: "completed",
  createdAt: { $gte: ISODate("2025-01-01") }
})
.sort({ total: -1 })
.skip(100)
.limit(20)

// 优化后(5ms)
db.orders.createIndex({
  status: 1,
  createdAt: -1,
  total: -1
})

db.orders.find(
  { status: "completed", createdAt: { $gte: ISODate("2025-01-01") } },
  { _id: 0, orderId: 1, total: 1, customer: 1 }
)
.sort({ total: -1 })
.skip(100)
.limit(20)
.explain()
{
  "totalDocsExamined": 20,
  "totalKeysExamined": 120,
  "stage": "PROJECTION_COVERED"
}

总结:查询分析黄金三问

问题检查点
1. 有没有用索引?IXSCAN vs COLLSCAN
2. 扫了多少文档?totalDocsExamined
3. 是不是覆盖查询?PROJECTION_COVERED + _id: 0

你现在可以:

// 一键分析任何查询
db.collection.find(...).explain("executionStats")

把你的慢查询贴给我,我 30 秒出优化方案:

  1. 查询语句find / aggregate
  2. 集合大小(文档数)
  3. 当前索引getIndexes()

回复 3 行代码,我生成:索引 + 优化查询 + explain 对比!

文章已创建 2371

发表回复

您的邮箱地址不会被公开。 必填项已用 * 标注

相关文章

开始在上面输入您的搜索词,然后按回车进行搜索。按ESC取消。

返回顶部