MongoDB: 性能优化与监控:生产级调优
最后更新:2026-08-26
性能优化与监控是生产环境的最后一公里——掌握它能避免 90% 的性能事故。
1. 你将学到
- 连接池调优
- 查询优化(hint / projection / lean)
- 慢查询日志分析
- APM 监控工具
- 生产部署 checklist
graph LR
App[Node.js 应用] -->|请求| Pool[Connection Pool<br/>maxPoolSize=50]
Pool -->|连接 1| M1[mongod<br/>Primary]
Pool -->|连接 2| M2[mongod<br/>Secondary]
Pool -->|连接 3| M3[mongod<br/>Secondary]
M1 -->|复制| M2
M1 -->|复制| M3
App -.->|explain| M1
App -.->|indexStats| M1
style Pool fill:#d4edda
style M1 fill:#cce5ff
2. 连接池调优
连接池为什么重要? MongoDB 每建立一个 TCP 连接需要:3 次握手 + SCRAM 认证(3 次往返)+ 会话初始化——约 10-50ms。在高并发场景下,如果没有连接池,每请求新建连接会导致连接风暴,拖垮数据库。连接池通过预热和复用连接,将连接获取时间降到 < 1ms。
连接池工作机制:
graph LR
subgraph "Node.js 应用"
R1[请求1] -->|借出| Pool[(连接池<br/>min=5 max=50)]
R2[请求2] -->|借出| Pool
R3[请求3] -->|排队等待| Queue[等待队列<br/>waitQueueTimeoutMS]
end
Pool -->|活跃连接| Active[mongod Primary]
Pool -->|空闲连接| Idle[空闲回收<br/>maxIdleTimeMS]
R1 -.->|归还| Pool
R3 -.->|获取归还连接| Pool
style Pool fill:#d4edda
style Queue fill:#fff3cd
style Active fill:#cce5ff
连接池参数调优指南:
| 参数 | 默认值 | 调优策略 | 过大风险 | 过小风险 |
|---|---|---|---|---|
| maxPoolSize | 100 | 按并发峰值 × 1.5 | 耗尽数据库连接 | 请求排队超时 |
| minPoolSize | 0 | 设为峰值 × 10% | 启动慢、占内存 | 冷启动延迟 |
| maxIdleTimeMS | 0 | 30000(30秒) | 频繁新建连接 | 连接泄漏 |
| waitQueueTimeoutMS | 0 | 10000(10秒) | 请求积压 | 请求永久等待 |
| serverSelectionTimeoutMS | 30000 | 5000 | 长时间卡住 | 快速失败 |
连接数计算公式:maxPoolSize = 并发峰值 × 平均查询时间(秒) × 安全系数(1.5)。如并发 100,平均查询 0.1s,则 maxPoolSize = 100 × 0.1 × 1.5 = 15。
// === mongoose 连接池配置 ===
mongoose.connect(uri, {
maxPoolSize: 50, // 最大连接数(默认 100)
minPoolSize: 5, // 最小连接数(默认 0)
maxIdleTimeMS: 30000, // 连接空闲超时(默认无限)
waitQueueTimeoutMS: 10000 // 等待连接超时
});
// === 监控连接池 ===
const db = mongoose.connection.db;
const stats = await db.admin().command({ serverStatus: 1 });
console.log('Connections:', stats.connections);
| maxPoolSize | 适用 |
|---|---|
| 10-20 | 小型应用(< 1000 DAU) |
| 30-50 | 中型应用(1K-100K DAU) |
| 50-100 | 大型应用(> 100K DAU) |
| 100+ | 超大流量(需谨慎,避免耗尽) |
3. 查询优化
查询优化方法论:查询优化的核心目标是"减少数据库工作量"——扫描更少的文档、传输更少的字段、跳过不必要的序列化。优化步骤:① 用 explain() 诊断问题;② 确认索引是否命中;③ 减少返回字段;④ 减少返回条数;⑤ 跳过不必要的 mongoose 开销。
查询优化四板斧:
| 优化手段 | 原理 | 效果 | 代码 |
|---|---|---|---|
| hint() 强制索引 | 绕过查询优化器的错误选择 | COLLSCAN → IXSCAN | .hint({field: 1}) |
| select() 投影 | 只返回必要字段 | 传输量 ↓90%+ | .select('sku title price') |
| lean() 跳过 hydrate | 不构造 mongoose Document | 性能 ↑5x | .lean() |
| limit() 限制条数 | 避免返回过多文档 | 内存 ↓ | .limit(20) |
常见慢查询模式与修复:
| 慢查询模式 | 原因 | 修复方案 |
|---|---|---|
| COLLSCAN(全表扫描) | 缺索引或索引未命中 | 创建合适索引 + hint() |
| 返回过多字段 | find() 无 select |
加 .select() 投影 |
| $where + JS 执行 | 每文档执行 JS 函数 | 改用原生操作符 |
| $regex 无锚定 | 无法用索引 | 加 ^ 前缀锚定 |
| 深分页 skip | skip 大量数据很慢 | 改用 cursor 分页 |
| N+1 查询 | 循环中逐个查询 | 改用 $in 或 $lookup |
(1) hint() 强制索引
// === 强制使用索引 ===
const products = await Product.find({ category: 'Electronics' })
.hint({ category: 1 });
// === mongoose 等价 ===
const products = await Product.find({ category: 'Electronics' })
.hint('category_1');
(2) projection 减少字段
// === 只查询必要字段 ===
const products = await Product.find()
.select('sku title price') // 不查 description, images 等
.lean();
// === 减少网络传输 90%+ ===
(3) lean() 跳过 hydrate
// === 普通查询:构造 mongoose Document(慢)===
const products = await Product.find();
// === lean():直接返回 plain object(快)===
const products = await Product.find().lean();
(4) 避免 $where
// ❌ 慢:$where 执行 JavaScript
db.products.find({ $where: 'this.price > 100' });
// ✅ 快:使用 $gt
db.products.find({ price: { $gt: 100 } });
4. 慢查询分析
慢查询分析流程:发现慢查询→启用 Profiler→分析 explain()→定位瓶颈→优化→验证。Profiling Level 分 3 级:0(关闭)、1(记录慢查询)、2(记录所有查询)。生产环境用 Level 1,Level 2 有性能开销。
慢查询诊断工作流:
graph TD
Start[发现慢查询] --> Enable[启用 Profiling Level 1<br/>slowms=100]
Enable --> Collect[收集慢查询日志<br/>system.profile]
Collect --> Explain[explain executionStats<br/>分析执行计划]
Explain --> Stage{Stage 类型?}
Stage -->|COLLSCAN| AddIdx[添加索引]
Stage -->|IXSCAN| CheckProj[检查投影/条数]
Stage -->|FETCH| OptProj[优化 select]
AddIdx --> Verify[验证优化效果]
CheckProj --> Verify
OptProj --> Verify
Verify --> Done[性能达标 ✅]
style COLLSCAN fill:#f8d7da
style Done fill:#d4edda
explain() 输出关键字段:
| 字段 | 含义 | 健康值 | 异常值 |
|---|---|---|---|
| stage | 扫描方式 | IXSCAN | COLLSCAN |
| totalKeysExamined | 索引键扫描数 | ≈ nReturned | >> nReturned |
| totalDocsExamined | 文档扫描数 | ≈ nReturned | >> nReturned |
| nReturned | 返回文档数 | — | — |
| executionTimeMillis | 执行时间 | < 100ms | > 1000ms |
| indexUsed | 使用的索引 | 复合索引名 | 无(COLLSCAN) |
黄金比例:totalDocsExamined : nReturned ≈ 1:1。如果扫描了 10000 条只返回 20 条,说明索引不够精确。
// === 启用慢查询日志 ===
mongoose.connection.db.admin().command({
setParameter: 1,
slowms: 100 // 记录 > 100ms 的查询
});
// === mongoose 中启用 debug ===
mongoose.set('debug', true);
// 输出所有查询到 console
// === mongoose-debug 慢查询日志 ===
mongoose.set('debug', (collectionName, method, query, doc) => {
const start = Date.now();
console.log(`${collectionName}.${method}(${JSON.stringify(query)})`);
});
5. 索引优化
索引优化核心原则:索引不是越多越好——每个索引占用磁盘空间、增加写入开销(每次 insert/update/delete 都要更新索引)、占用内存(MongoDB 尽量把索引放在 RAM)。优化索引的关键是:建有用的索引、删无用的索引、选对索引类型。
索引健康度检查指标:
| 指标 | 获取方式 | 健康值 | 异常值 |
|---|---|---|---|
| 索引使用率 | $indexStats | accesses.ops > 0 | ops = 0(未使用) |
| 索引大小 | db.stats() | < RAM 的 50% | > RAM(频繁换页) |
| 索引数量 | getIndexes() | < 10 / 集合 | > 20(过多) |
| 写入延迟 | serverStatus | < 10ms | > 100ms(索引拖慢写入) |
索引优化决策:
| 场景 | 操作 | 原因 |
|---|---|---|
| ops=0 的索引 | 删除 | 纯浪费空间和写入性能 |
| 功能重复的索引 | 保留复合索引,删单字段 | {a:1,b:1} 已覆盖 {a:1} |
| 低基数索引 | 删除或改部分索引 | isActive 只有2值,区分度低 |
| 查询未走索引 | 添加或 hint() | COLLSCAN 性能灾难 |
// === 索引使用分析 ===
db.products.aggregate([{ $indexStats: {} }]);
// 找出 unused indexes
// === 慢查询中的索引使用 ===
db.system.profile.find({ millis: { $gt: 100 } })
.sort({ ts: -1 })
.limit(20)
.forEach(profile => {
console.log('Query:', profile.command.find);
console.log('Plan:', profile.planSummary);
console.log('Time:', profile.millis, 'ms');
});
6. APM 监控工具
为什么需要 APM? 生产环境的问题不会提前通知你——连接数飙升、慢查询增长、内存泄漏都是渐进发生的。APM(Application Performance Monitoring)提供实时指标采集、可视化仪表盘、阈值告警,让你在用户投诉之前发现并解决问题。
监控指标金字塔:
graph TB
L4[业务指标<br/>订单量/错误率/响应时间] --> L3[应用指标<br/>QPS/延迟/Node.js 内存]
L3 --> L2[数据库指标<br/>连接数/慢查询/索引命中率]
L2 --> L1[基础设施<br/>CPU/内存/磁盘/网络]
style L4 fill:#d4edda
style L1 fill:#fff3cd
核心监控指标:
| 指标类别 | 具体指标 | 告警阈值 | 采集方式 |
|---|---|---|---|
| 连接 | 当前连接数 | > maxPoolSize × 80% | serverStatus.connections |
| 查询 | 慢查询数 | > 10/min | system.profile |
| 查询 | 平均查询时间 | > 200ms | mongoose debug / APM |
| 索引 | 索引命中率 | < 95% | $indexStats |
| 内存 | Resident Memory | > 可用 RAM 80% | serverStatus.mem |
| 磁盘 | 磁盘使用率 | > 80% | db.stats() |
| 副本 | 复制延迟 | > 10s | rs.status().lag |
| 工具 | 特点 | 适用 |
|---|---|---|
| MongoDB Atlas Monitoring | 官方,自带 | Atlas 用户 |
| Prometheus + mongo_exporter | 开源,自建 | 大型生产 |
| Datadog APM | 商业,全栈 | 企业 |
| New Relic | 商业,全栈 | 企业 |
| Prometheus + Grafana | 开源,免费 | 自建监控 |
7. 生产部署 Checklist
生产环境的核心关切:生产部署不是"代码能跑就行",而是要确保:高可用(单点故障不影响服务)、数据安全(不丢数据、不泄露)、可观测(出问题能快速定位)、可恢复(备份可还原)。
生产部署四大支柱:
| 支柱 | 目标 | 关键措施 |
|---|---|---|
| 高可用 | 99.9%+ 可用性 | 副本集 3 节点 + 自动故障转移 |
| 数据安全 | 不丢数据 + 不泄露 | Write Concern majority + TLS + RBAC |
| 可观测性 | 5 分钟内定位问题 | 日志 + 指标 + 告警 + APM |
| 可恢复性 | RPO < 1h, RTO < 4h | 定时备份 + PITR + 恢复演练 |
## 10. 性能优化 Checklist
---
### (1) 数据库
- [ ] 副本集 3 节点(高可用)
- [ ] Write Concern majority(数据不丢)
- [ ] Read Preference primaryPreferred
- [ ] 慢查询监控启用
- [ ] 索引使用率监控
- [ ] 连接池 maxPoolSize 设置
### (2) 应用层
- [ ] mongoose lean() 用于纯查询
- [ ] projection 只查必要字段
- [ ] bulkWrite 批量操作
- [ ] 避免 $where / $regex 无锚定
- [ ] 错误处理中间件
- [ ] 健康检查端点 /healthz
### (3) 运维
- [ ] 每日 mongodump 备份
- [ ] 备份保留 7-30 天
- [ ] 监控告警(连接数 / 慢查询 / 磁盘)
- [ ] TLS/SSL 加密传输
- [ ] SCRAM + RBAC 权限控制
- [ ] Atlas PITR 或 oplog 持续备份
8. 实战:综合性能调优
性能调优实战方法论:性能调优不是拍脑袋优化,而是"测量→分析→优化→验证"的闭环。先用 explain() 测量当前性能,定位瓶颈(全表扫描?传输过多?hydrate 开销?),针对性优化,再用 explain() 验证效果。
性能调优闭环:
graph LR
A[测量: explain + 慢日志] --> B[分析: 瓶颈定位]
B --> C[优化: 索引/投影/lean]
C --> D[验证: 再 explain]
D -->|未达标| A
D -->|达标| E[上线 ✅]
style A fill:#cce5ff
style C fill:#d4edda
style E fill:#d4edda
ShopHub 优化案例:TechCorp 的 Alice 优化商品列表 API,从 3s 降到 50ms——① explain() 发现 COLLSCAN → 加复合索引 {category:1, isActive:1, createdAt:-1};② 返回所有字段 → select() 只查 5 个;③ 返回 mongoose Document → 加 lean();④ find 和 count 串行 → Promise.all 并行。
// === 优化前的慢查询 ===
app.get('/api/products', async (req, res) => {
const products = await Product.find({ isActive: true });
// 100 万文档全表扫描,~3 秒
res.json(products);
});
// === 优化后 ===
app.get('/api/products', async (req, res) => {
const { page = 1, limit = 20, category, search } = req.query;
// 1. 构建索引优化查询
const query = { isActive: true };
if (category) query.category = category;
if (search) query.title = new RegExp(search, 'i');
// 2. 投影 + lean + limit
const products = await Product.find(query)
.select('sku title price thumbnail') // 投影
.hint({ isActive: 1, category: 1 }) // 强制索引
.limit(Math.min(+limit, 100)) // 限制最大
.skip((+page - 1) * +limit)
.lean(); // 性能优化
// 3. 并行 count
const total = await Product.countDocuments(query);
res.json({ data: products, meta: { page: +page, limit: +limit, total } });
});
// 优化后:~50ms(性能 ↑60x)
▶ 示例 1:explain() 诊断 + 索引优化
// === 场景:ShopHub 商品列表查询慢,Alice 用 explain 诊断 ===
// 1. 诊断当前查询
const explain = await Product.find({ category: 'Electronics', isActive: true })
.sort({ createdAt: -1 })
.limit(20)
.explain('executionStats');
console.log('Stage:', explain.queryPlanner.winningPlan.stage);
// 输出:COLLSCAN ❌ 全表扫描!
console.log('Docs examined:', explain.executionStats.totalDocsExamined);
// 输出:1000000(扫描了全部 100 万文档)
console.log('Docs returned:', explain.executionStats.nReturned);
// 输出:20(只返回 20 条)
console.log('Time:', explain.executionStats.executionTimeMillis, 'ms');
// 输出:3200ms(太慢!)
// 2. 创建复合索引
db.products.createIndex({ category: 1, isActive: 1, createdAt: -1 });
// 3. 再次 explain 验证
const explain2 = await Product.find({ category: 'Electronics', isActive: true })
.sort({ createdAt: -1 })
.limit(20)
.hint({ category: 1, isActive: 1, createdAt: -1 })
.explain('executionStats');
console.log('Stage:', explain2.queryPlanner.winningPlan.stage);
// 输出:IXSCAN ✅ 使用索引!
console.log('Docs examined:', explain2.executionStats.totalDocsExamined);
// 输出:20(精确扫描)
console.log('Time:', explain2.executionStats.executionTimeMillis, 'ms');
// 输出:5ms(↑640x 性能提升!)
输出:explain() 诊断发现 COLLSCAN → 创建复合索引 → IXSCAN,查询从 3200ms 降到 5ms。
▶ 示例 2:综合性能调优实战(连接池 + 索引 + lean + 监控)
// === 场景:商品列表 API 性能优化(3s → 50ms)===
// === 优化前(慢)===
app.get('/api/products', async (req, res) => {
const products = await Product.find(); // 全表扫描 + 返回所有字段
res.json(products);
});
// 100 万文档,~3000ms,~50MB 数据
// === 优化后(快)===
// 1. 启用 mongoose debug(开发时观察查询)
mongoose.set('debug', (coll, method, query) => {
console.log(`${coll}.${method}(${JSON.stringify(query)})`);
});
// 2. 优化连接池
mongoose.connect(uri, {
maxPoolSize: 50, // 根据并发调整
minPoolSize: 5,
maxIdleTimeMS: 30000,
waitQueueTimeoutMS: 10000
});
// 3. 启用慢查询日志(>100ms)
mongoose.connection.db.admin().command({
setParameter: 1,
slowms: 100
});
// 4. 创建合适的索引
db.products.createIndex({ category: 1, isActive: 1, createdAt: -1 });
db.products.createIndex({ sku: 1 }, { unique: true });
db.products.createIndex({ title: 'text', description: 'text' });
// 5. 优化查询:projection + lean + hint + limit
app.get('/api/products', async (req, res) => {
const { page = 1, limit = 20, category, search } = req.query;
const query = { isActive: true };
if (category) query.category = category;
if (search) query.title = new RegExp(search, 'i');
const [products, total] = await Promise.all([
Product.find(query)
.select('sku title price thumbnail rating') // 投影:仅返回 5 个字段
.hint({ category: 1, isActive: 1, createdAt: -1 }) // 强制索引
.sort({ createdAt: -1 })
.skip((page - 1) * limit)
.limit(Math.min(+limit, 100))
.lean(), // 跳过 mongoose hydrate
Product.countDocuments(query)
]);
res.json({
success: true,
data: products,
meta: { page: +page, limit: +limit, total, pages: Math.ceil(total / limit) }
});
});
// 100 万文档,~50ms(性能 ↑60x),~200KB 数据(减少 99.6%)
// === 6. explain() 验证索引生效 ===
const explain = await Product.find({ category: 'Electronics', isActive: true })
.sort({ createdAt: -1 })
.limit(20)
.explain('executionStats');
console.log('Stage:', explain.queryPlanner.winningPlan.stage);
// 输出:IXSCAN(使用了索引)
console.log('Docs examined:', explain.executionStats.totalDocsExamined);
console.log('Keys examined:', explain.executionStats.totalKeysExamined);
console.log('Returned:', explain.executionStats.nReturned);
console.log('Time:', explain.executionStats.executionTimeMillis, 'ms');
// === 7. 监控告警 ===
// 7.1 监控连接数
const connStatus = await mongoose.connection.db.admin().command({ serverStatus: 1 });
if (connStatus.connections.current > 1000) {
console.warn(`⚠️ 连接数过高: ${connStatus.connections.current}`);
// 发送告警(邮件/钉钉/Slack)
}
// 7.2 监控慢查询
const slowQueries = await mongoose.connection.db.collection('system.profile')
.find({ millis: { $gt: 100 } })
.sort({ ts: -1 })
.limit(10)
.toArray();
slowQueries.forEach(q => {
console.log(`[${q.ts}] ${q.command.find}: ${q.millis}ms`);
console.log(` Plan: ${q.planSummary}`);
});
// 7.3 索引使用率
const indexStats = await mongoose.connection.db.collection('products')
.aggregate([{ $indexStats: {} }])
.toArray();
const unused = indexStats.filter(s => s.accesses.ops === 0);
unused.forEach(i => {
console.log(`⚠️ 未使用索引: ${i.name}`);
// 自动删除(生产环境谨慎)
// await mongoose.connection.db.collection('products').dropIndex(i.name);
});
// === 8. APM 集成(Datadog/New Relic)===
const tracer = require('dd-trace').init();
tracer.use('mongoose', { service: 'shopdb' });
// 所有 mongoose 查询自动追踪,性能数据上报 Datadog
输出:连接池 + 索引 + lean + projection 综合优化,查询性能从 3s 降到 50ms(↑60x),数据传输减少 99.6%。
▶ 示例 3:慢查询日志 + Profiler + 索引使用分析
性能优化的第一步是定位问题——哪个查询慢?为什么慢?是缺索引还是索引没用上?MongoDB 提供三层诊断工具:慢查询日志(mongod 层记录)、Database Profiler(详细记录查询计划)、$indexStats(索引使用统计)。本示例实现完整的慢查询诊断流水线:发现 → 分析 → 优化 → 验证。
const mongoose = require('mongoose');
// === 1. 开启 Profiler(三种模式)===
// 模式 0:关闭(默认);模式 1:只记录慢查询;模式 2:记录所有操作
await mongoose.connection.db.adminCommand({
profile: 1, // 模式 1:只记录慢查询
slowms: 50, // 慢查询阈值 50ms
sampleRate: 1 // 采样率 100%
});
// === 2. 查询 Profiler 数据(system.profile 集合)===
async function getSlowQueries(minutes = 30) {
const since = new Date(Date.now() - minutes * 60 * 1000);
const slowQueries = await mongoose.connection.db
.collection('system.profile')
.find({
ts: { $gte: since },
op: { $in: ['query', 'update', 'remove', 'getmore'] },
'command.aggregate': { $exists: false } // 排除聚合(另分析)
})
.sort({ millis: -1 })
.limit(20)
.toArray();
return slowQueries.map(q => ({
timestamp: q.ts,
operation: q.op,
collection: q.ns,
query: JSON.stringify(q.query || q.command),
durationMs: q.millis,
docsExamined: q.docsExamined,
docsReturned: q.nreturned,
indexUsed: q.planSummary,
efficiency: q.docsExamined > 0
? (q.nreturned / q.docsExamined * 100).toFixed(1) + '%'
: 'N/A'
}));
}
// === 3. 索引使用统计 ===
async function analyzeIndexUsage(collectionName) {
const stats = await mongoose.connection.db
.collection(collectionName)
.aggregate([{ $indexStats: {} }])
.toArray();
return stats.map(s => ({
name: s.name,
accesses: s.accesses.ops,
since: s.accesses.since,
isUnused: s.accesses.ops === 0,
keys: Object.keys(s.key),
size: s.size || 'N/A'
}));
}
// === 4. explain() 诊断工具函数 ===
async function diagnoseQuery(collection, query, sort = {}) {
const explanation = await mongoose.connection.db
.collection(collection)
.find(query)
.sort(sort)
.explain('executionStats');
const stage = explanation.queryPlanner.winningPlan;
const stats = explanation.executionStats;
return {
totalDocsExamined: stats.totalDocsExamined,
totalDocsReturned: stats.totalDocsReturned,
executionTimeMs: stats.executionTimeMillis,
indexUsed: stage.inputStage?.indexName || 'COLLSCAN',
isCollectionScan: stage.stage === 'COLLSCAN',
efficiency: stats.totalDocsExamined > 0
? (stats.totalDocsReturned / stats.totalDocsExamined * 100).toFixed(1) + '%'
: 'N/A',
recommendation: getRecommendation(stage, stats)
};
}
function getRecommendation(stage, stats) {
const ratio = stats.totalDocsReturned / Math.max(stats.totalDocsExamined, 1);
if (stage.stage === 'COLLSCAN') return '缺少索引!添加匹配查询条件的索引';
if (ratio < 0.01) return '索引效率低!查询扫描了大量文档但返回很少,考虑更精确的索引';
if (stats.executionTimeMillis > 100) return '查询仍较慢,考虑添加覆盖索引或优化排序';
return '查询性能良好';
}
// === 5. 自动索引建议 ===
async function suggestIndexes(collectionName) {
const slowQueries = await mongoose.connection.db
.collection('system.profile')
.find({ ns: `${mongoose.connection.name}.${collectionName}` })
.sort({ millis: -1 })
.limit(10)
.toArray();
const fieldFrequency = {};
slowQueries.forEach(q => {
const fields = q.query ? Object.keys(q.query) : [];
fields.forEach(f => { fieldFrequency[f] = (fieldFrequency[f] || 0) + 1; });
});
const sortedFields = Object.entries(fieldFrequency)
.sort((a, b) => b[1] - a[1])
.map(([field, count]) => ({ field, queryCount: count }));
return {
collection: collectionName,
topQueryFields: sortedFields.slice(0, 5),
suggestedIndex: sortedFields.slice(0, 3).map(f => f.field).reduce((idx, f) => {
idx[f] = 1; return idx;
}, {})
};
}
// === 6. 定时报告 ===
setInterval(async () => {
const slowQueries = await getSlowQueries(5);
if (slowQueries.length > 0) {
console.log(`⚠️ 最近 5 分钟慢查询 (${slowQueries.length} 条):`);
slowQueries.forEach(q => {
console.log(` ${q.collection} | ${q.durationMs}ms | 效率: ${q.efficiency} | 索引: ${q.indexUsed}`);
});
}
}, 5 * 60 * 1000);
输出:Profiler 自动记录 >50ms 的慢查询,diagnoseQuery 用 explain() 分析查询计划(COLLSCAN/IXSCAN),analyzeIndexUsage 找出未使用索引,suggestIndexes 根据慢查询频率自动建议索引。定时报告每 5 分钟汇总慢查询。
性能诊断的工作流:1. 开启 Profiler(模式 1 + slowms: 50)收集慢查询;2. 用 explain('executionStats') 分析最慢的查询——确认是 COLLSCAN(缺索引)还是低效索引(扫描太多文档);3. 添加索引后重新 explain() 验证;4. 用 $indexStats 定期检查未使用索引并清理——未使用索引浪费内存和写入性能;5. 线上慎用 Profiler 模式 2(记录所有操作有性能开销),用模式 1 + 合理 slowms 即可。
❓ 常见问题
query.explain('executionStats') 查看 stage:IXSCAN(有索引)/ COLLSCAN(全表扫描)。📖 小节
- 连接池调优:maxPoolSize / minPoolSize
- 查询优化:hint / projection / lean / 避免 $where
- 慢查询分析:setProfilingLevel / mongoose debug
- 索引使用监控:$indexStats / system.profile
- APM 工具:Atlas / Prometheus / Datadog
- 生产 Checklist:副本集 + 备份 + 监控 + 安全
📝 作业
- 基础题(⭐):用 lean() 优化商品列表 API,对比性能差异。
- 基础题(⭐):启用慢查询日志,分析 Top 10 慢查询。
- 进阶题(⭐⭐):用 hint 强制索引,对比 COLLSCAN vs IXSCAN 性能。
- 进阶题(⭐⭐):分析 $indexStats,找出 unused indexes 并删除。
- 挑战题(⭐⭐⭐):完整性能调优(连接池 + 索引 + lean + 监控),对比前后性能。