1. 索引缺失的典型场景与解决方案

在电商订单系统中,当我们需要根据用户ID+时间段查询订单时,未创建索引的查询就像在图书馆找书不带目录。假设我们有一个包含500万订单的集合:

// 未优化查询示例(执行时间:1200ms)
db.orders.find({
  userId: "U10001",
  createdAt: {
    $gte: ISODate("2023-01-01"),
    $lt: ISODate("2023-06-01")
  }
}).explain("executionStats") // 查看执行计划

// 创建复合索引(执行时间降至25ms)
db.orders.createIndex({ userId: 1, createdAt: -1 }, {
  background: true, // 后台创建不影响业务
  expireAfterSeconds: 31536000 // 自动过期数据(可选)
})

技术栈:MongoDB 5.0+

应用场景:高频查询字段组合、范围查询、排序操作

优势

  • 查询速度提升10-100倍
  • 减少内存占用
  • 支持覆盖查询

注意事项

  • 索引维护需要额外存储空间(约数据量的10%-20%)
  • 写操作会触发索引更新,高写入场景需要谨慎
  • 避免创建重复索引(如{a:1, b:1}和{b:1, a:1})

2. 内存瓶颈的识别与处理

当处理包含地理空间数据的物流系统时,我们可能遇到内存不足的警告:

// 问题查询示例(内存溢出)
db.warehouses.aggregate([
  {
    $geoNear: {
      near: { type: "Point", coordinates: [121.47, 31.23] },
      distanceField: "dist",
      maxDistance: 5000,
      spherical: true
    }
  },
  {
    $lookup: { // 关联库存数据
      from: "inventory",
      localField: "_id",
      foreignField: "warehouseId",
      as: "stocks"
    }
  },
  { $unwind: "$stocks" },
  { $match: { "stocks.quantity": { $gt: 0 } } }
])

优化策略

  1. 使用投影限制返回字段
  2. 分批处理数据
  3. 增加可用内存
// 优化后查询
db.warehouses.aggregate([
  {
    $geoNear: {
      near: { type: "Point", coordinates: [121.47, 31.23] },
      distanceField: "dist",
      maxDistance: 5000,
      spherical: true
    }
  },
  {
    $lookup: {
      from: "inventory",
      let: { wid: "$_id" },
      pipeline: [ // 子管道优化
        { $match: { 
          $expr: { $eq: ["$warehouseId", "$$wid"] },
          quantity: { $gt: 0 }
        }},
        { $project: { _id: 0, itemId: 1, quantity: 1 } }
      ],
      as: "stocks"
    }
  },
  { $limit: 100 } // 分页限制
])

技术栈:MongoDB 4.2+(支持聚合管道中的$lookup)


3. 分页查询的性能陷阱

新闻资讯系统的深度分页问题:

// 传统分页(性能随页码增加下降)
db.articles.find().skip(1000000).limit(10) 

// 优化方案:游标分页(执行时间恒定)
const lastArticleDate = ISODate("2023-08-20T15:30:00Z");
db.articles.find({
  createdAt: { $lt: lastArticleDate }
})
.sort({ createdAt: -1 })
.limit(10)

性能对比: | 方案 | 10页耗时 | 1000页耗时 | 内存消耗 | |---------------|----------|------------|----------| | 传统分页 | 50ms | 1200ms | 高 | | 游标分页 | 20ms | 25ms | 低 |


4. 聚合管道的优化技巧

在用户行为分析场景中,我们常需要处理复杂的统计:

// 原始聚合管道(执行时间:8秒)
db.user_actions.aggregate([
  { $match: { eventType: "purchase" } },
  { $group: {
    _id: "$userId",
    totalSpent: { $sum: "$amount" },
    orderCount: { $sum: 1 }
  }},
  { $sort: { totalSpent: -1 } },
  { $limit: 100 }
])

// 优化后版本(执行时间:1.2秒)
db.user_actions.aggregate([
  { 
    $match: { 
      eventType: "purchase",
      amount: { $gt: 0 } // 添加过滤条件
    } 
  },
  { 
    $group: {
      _id: "$userId",
      totalSpent: { $sum: "$amount" },
      orderCount: { $sum: 1 },
      lastPurchase: { $max: "$timestamp" } // 添加必要字段
    }
  },
  { 
    $project: { // 字段裁剪
      _id: 1,
      totalSpent: 1,
      orderCount: 1
    }
  },
  { $sort: { totalSpent: -1 } },
  { $limit: 100 }
])

优化要点:

  1. 提前过滤无效数据
  2. 减少中间阶段的文档体积
  3. 利用索引覆盖查询

5. 连接查询的性能优化

在电商平台的订单-商品关联查询中:

// 低效的$lookup使用
db.orders.aggregate([
  { $lookup: {
    from: "products",
    localField: "items.productId",
    foreignField: "_id",
    as: "productDetails"
  }},
  { $unwind: "$productDetails" }
])

// 优化方案:预关联设计
// 订单文档结构优化
{
  _id: ObjectId("..."),
  userId: "U1001",
  items: [{
    productId: "P100",
    name: "智能手表", // 冗余常用字段
    price: 599.00
  }]
}

性能对比: | 方案 | 响应时间 | 内存占用 | 扩展性 | |------------|----------|----------|--------| | 传统关联 | 450ms | 高 | 好 | | 预关联设计 | 80ms | 低 | 一般 |


6. 事务处理的性能调优

金融交易系统中的典型场景:

// 未优化的转账事务
session.startTransaction();
try {
  const fromAcc = db.accounts.findOne({ _id: "A1001" });
  const toAcc = db.accounts.findOne({ _id: "A1002" });
  
  db.accounts.updateOne(
    { _id: "A1001" },
    { $inc: { balance: -500 } }
  );
  
  db.accounts.updateOne(
    { _id: "A1002" },
    { $inc: { balance: 500 } }
  );
  
  session.commitTransaction();
} catch (e) {
  session.abortTransaction();
}

// 优化方案:批量操作+写关注调整
const bulkOps = [
  {
    updateOne: {
      filter: { _id: "A1001" },
      update: { $inc: { balance: -500 } }
    }
  },
  {
    updateOne: {
      filter: { _id: "A1002" },
      update: { $inc: { balance: 500 } }
    }
  }
];

db.accounts.bulkWrite(bulkOps, {
  writeConcern: { w: "majority", wtimeout: 5000 }
});

技术栈:MongoDB 4.2+(支持分布式事务)


7. 全文搜索的优化实践

在内容管理系统中实现高效搜索:

// 创建全文索引
db.articles.createIndex({
  title: "text",
  content: "text"
}, {
  weights: {
    title: 3,  // 标题权重更高
    content: 1
  },
  default_language: "chinese" // 中文分词
});

// 优化后的搜索查询
db.articles.find({
  $text: { 
    $search: "数据库 优化技巧 -故障", // 包含排除语法
    $language: "chinese"
  }
}, {
  score: { $meta: "textScore" } // 相关性评分
}).sort({ score: { $meta: "textScore" } })

8. 分片集群的查询优化

超大规模用户系统的分片策略:

// 用户表分片配置
sh.enableSharding("social_network")
sh.shardCollection("social_network.users", {
  region: 1,       // 地域作为分片键
  _id: "hashed"    // 复合分片键
})

// 查询路由优化
db.users.find({
  region: "east",
  lastLogin: { $gt: ISODate("2023-07-01") }
}).explain("queryPlanner") // 查看是否命中分片

分片键选择原则:

  • 高基数字段
  • 写分布均衡
  • 匹配查询模式

9. 执行计划分析技巧

使用explain()诊断查询性能:

const explainResult = db.products.explain("executionStats")
  .find({
    category: "electronics",
    price: { $lte: 1000 },
    rating: { $gte: 4 }
  })
  .sort({ sales: -1 })

// 关键指标解读:
{
  "executionTimeMillis": 120,
  "totalKeysExamined": 5842,
  "totalDocsExamined": 120,
  "executionStages": {
    "stage": "FETCH",      // 阶段类型
    "filter": {...},       // 过滤条件
    "inputStage": {...}    // 前序阶段
  }
}

10. 持续优化与监控体系

建立性能监控仪表盘:

// 慢查询日志分析
db.setProfilingLevel(1, { slowms: 100 }) // 记录超过100ms的操作

// 实时性能指标获取
db.runCommand({ serverStatus: 1 })
  .metrics.operation  // 操作计数器
  .queryExecutor      // 查询执行统计
  .wiredTiger         // 存储引擎指标

推荐监控指标:

  • 操作延迟(OPCOUNTER)
  • 缓存命中率
  • 队列等待时间
  • 锁竞争情况

技术总结与建议

  1. 预防优于治疗:在设计阶段就考虑索引策略
  2. 数据即代码:版本化管理索引和聚合管道
  3. 监控常态化:建立性能基线,设置智能告警
  4. 渐进式优化:优先解决TOP N慢查询
  5. 容量规划:预留30%的性能余量应对突发流量

通过本文的实战场景,我们系统性地梳理了MongoDB复杂查询的优化方法论。记住:没有银弹式的优化方案,只有最适合业务场景的解决方案。建议定期进行查询审查,结合EXPLAIN分析和真实监控数据,持续迭代优化策略。