MongoDB性能调优实战:从慢查询日志到Profiling工具
1. 慢查询日志定位性能瓶颈的第一道防线刚接触MongoDB时我最头疼的就是遇到查询突然变慢的情况。后来发现慢查询日志就像汽车的故障指示灯能快速告诉我哪里出了问题。设置慢查询日志只需要一条命令db.setProfilingLevel(1, 100)这个命令开启了慢查询记录功能其中数字1表示记录慢查询100表示执行时间超过100毫秒的操作会被记录。建议初次使用时设置较大的阈值如200ms等收集到足够数据后再逐步调低。查看记录的慢查询也很简单db.system.profile.find().sort({millis:-1}).limit(5)这个查询会返回最耗时的5个操作。我常用它来发现性能杀手比如有一次就发现一个全集合扫描查询居然用了2秒。实际分析时要注意几个关键字段op操作类型query/insert/update等ns操作的命名空间数据库.集合millis执行耗时毫秒planSummary执行计划概要注意生产环境建议定期清理system.profile集合避免日志膨胀影响性能。可以通过设置profile参数限制集合大小。2. Profiling工具数据库操作的X光机如果说慢查询日志是初步体检那么Profiling工具就是全身CT扫描。它有三级配置0关闭1记录慢操作2记录所有操作我一般先用级别1定位问题在复现问题时临时切换到级别2。比如排查一个间歇性慢查询时// 临时开启全量记录 db.setProfilingLevel(2) // 问题复现后立即调回 db.setProfilingLevel(1, 50)Profiling数据中最有价值的是执行计划。有次我发现一个查询用了COLLSCAN全表扫描通过添加索引后变成了IXSCAN索引扫描性能提升了20倍。分析时重点关注execStats详细的执行统计keysExamined扫描的索引键数量docsExamined扫描的文档数量一个实用技巧是结合explain()分析db.collection.find({...}).explain(executionStats)3. 索引优化从入门到精通索引是MongoDB性能的关键但常见误区是乱建索引。我踩过的坑包括给所有字段都建索引导致写入变慢创建重复索引如已有{a:1,b:1}又建{a:1}忽略索引选择性给性别字段建索引基本没用好的索引策略应该通过慢查询日志找出高频查询用explain()分析现有执行计划创建复合索引时遵循ESR原则Equality等值条件字段放前面Sort排序字段放中间Range范围查询字段放最后比如优化这个常见查询db.orders.find({ status: completed, createDate: {$gte: ISODate(2023-01-01)} }).sort({amount: -1})最佳索引应该是db.orders.createIndex({ status: 1, // E amount: -1, // S createDate: 1 // R })4. 高级调优技巧超越基础配置除了索引这些技巧也很有用连接池优化默认连接池大小可能不够特别是Node.js应用。可以通过监控db.serverStatus().connections调整// Mongoose设置示例 mongoose.connect(uri, { poolSize: 50, // 连接池大小 socketTimeoutMS: 30000 })内存配置MongoDB性能严重依赖内存建议确保工作集working set能放入内存使用db.collection.totalSize()估算集合大小通过free -h监控系统内存使用写入优化批量插入比单条插入快得多。实测插入10万条数据单条插入约5分钟批量插入每次1000条约15秒// 批量插入示例 var bulk db.items.initializeUnorderedBulkOp() for(let i0;i100000;i){ bulk.insert({_id:i, text:itemi}) if(i%10000) bulk.execute() } bulk.execute()5. 实战案例电商平台优化记去年优化过一个电商系统用户抱怨商品搜索时卡顿。通过Profiling发现主要问题商品查询没有使用索引分类聚合查询扫描全部文档用户评价分页查询效率低优化方案创建复合索引db.products.createIndex({ category:1, price:1, rating:-1 })重写聚合查询添加$match提前过滤db.products.aggregate([ {$match: {category: 电子产品}}, {$group: {_id: $brand, count: {$sum:1}}} ])使用find()limit()替代skip()做分页// 低效方式 db.reviews.find().skip(10000).limit(10) // 高效方式记住最后一条的_id db.reviews.find({_id: {$gt: lastId}}).limit(10)优化后页面加载时间从3秒降到300毫秒。关键是要持续监控我们设置了每周分析慢查询日志的机制。