0%

⚡ MongoDB 性能优化篇:索引、查询调优与分片最佳实践

1. 为什么需要优化?

MongoDB 虽然灵活,但如果不加优化,可能出现:

  • 查询性能低(扫描全表)

  • 分片热点(某一分片压力过大)

  • 内存占用高(缓存无效)

  • 写入延迟大(锁竞争)

优化的核心目标是:减少扫描量,减少锁竞争,充分利用索引与缓存

2. 索引优化(Index Optimization)

MongoDB 查询性能的关键就是索引。

2.1 索引类型

类型 作用 示例
单字段索引 针对单个字段 db.users.createIndex({ age: 1 })
复合索引 多个字段组合 db.users.createIndex({ name: 1, age: -1 })
唯一索引 防止重复值 db.users.createIndex({ email: 1 }, { unique: true })
文本索引 搜索文本 db.articles.createIndex({ content: “text” })
地理索引 地理位置查询 db.locations.createIndex({ loc: “2dsphere” })

2.2 索引命中原则

  1. 查询条件中用到的字段要有索引

  2. 复合索引遵循最左前缀原则

    例如:

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

    这个索引可用于:

    • { name: "Tom" }

    • { name: "Tom", age: 25 }
      但不能用于:

    • { age: 25 }(会全表扫描)

  3. 避免过多索引(写入成本高,内存占用大)

  4. 定期清理无用索引

    1
    db.users.dropIndex("name_1")

2.3 查看查询是否使用索引

1
db.users.find({ age: { $gt: 25 } }).explain("executionStats")

如果输出中 "stage""COLLSCAN" → 全表扫描,需要优化。
如果是 "IXSCAN" → 命中索引。

3. 查询优化(Query Optimization)

3.1 只取需要的字段

1
db.users.find({}, { name: 1, age: 1, _id: 0 })

减少网络传输与内存占用。

3.2 避免 $where、正则前缀模糊

1
2
3
4
5
// 慢(无法利用索引)
db.users.find({ name: /Tom/ })

// 快(利用索引)
db.users.find({ name: /^Tom/ })

3.3 提前过滤 $match

聚合时 $match 放前面,减少后续数据量:

1
2
3
4
db.orders.aggregate([
{ $match: { status: "completed" } }, // 放前面
{ $group: { _id: "$customerId", total: { $sum: "$amount" } } }
])

3.4 批量写入

1
db.collection.insertMany([...], { ordered: false })

并行插入数据,减少锁竞争。

4. 分片优化(Sharding Optimization)

4.1 选择合适的分片键

  • 高基数(cardinality)字段,例如 userId
    (避免低基数字段导致数据集中在少数分片)

  • 避免时间戳作为唯一分片键(会产生热点写入)

  • 考虑复合分片键,例如 { region: 1, userId: 1 }

4.2 预分片

大规模导入数据前,先预分片:

1
2
3

sh.enableSharding("mydb")
sh.shardCollection("mydb.users", { userId: 1 })

4.3 均衡分布

定期检查分片均衡:

1
sh.status()

如果不均衡,可手动触发:

1
sh.startBalancer()

5. 聚合优化(Aggregation Optimization)

5.1 $match 前置

将过滤条件尽量放在管道前端,减少后续处理数据量。

5.2 $project 提前裁剪字段

1
2
3
4
db.orders.aggregate([
{ $project: { orderId: 1, amount: 1, _id: 0 } },
{ $group: { _id: "$orderId", total: { $sum: "$amount" } } }
])

5.3 避免 $lookup 过多

跨集合关联查询代价大,可以考虑:

  • 数据冗余(反范式设计)

  • 预计算结果存储到新集合

6. 实测优化建议

我做过一次 1000 万条订单数据的优化对比:

优化前(无索引) 优化后(加索引 + $match 前置)
查询耗时:12.4s 查询耗时:0.8s
CPU 占用:95% CPU 占用:25%
内存占用:8GB 内存占用:3GB

7. 运维与监控

7.1 常用工具

  • mongostat → 实时查看 QPS、锁、缓存命中率

  • mongotop → 查看集合读写耗时

  • db.serverStatus() → 查看实例状态

7.2 性能监控平台

  • MongoDB Atlas(官方云监控)

  • Prometheus + Grafana(开源可视化)

8. 总结

MongoDB 性能优化的核心公式:

1
高效索引 + 合理分片键 + 精简查询字段 + 聚合前置过滤

建议开发时就养成:

  1. 先设计索引再写查询

  2. 定期 explain() 检查查询计划

  3. 高并发场景下,优先副本集 + 分片结合

  4. 聚合复杂计算时,考虑预计算表