一、为什么你的MongoDB查询突然变慢了?

最近有个做电商的朋友跑来诉苦,说他们的商品搜索功能突然变得特别卡。我一看监控,好家伙,一个简单的分类查询竟然要3秒多!这要放在双十一,服务器还不得直接罢工啊?

经过排查,发现问题出在索引失效上。这就好比你去图书馆找书,明明有目录索引,管理员却非要一本一本翻给你看,能不慢吗?MongoDB的索引失效问题,往往就是这种"有索引不用"的尴尬情况。

二、常见的索引失效场景

1. 查询条件不匹配索引顺序

// MongoDB示例:商品集合索引
db.products.createIndex({ category: 1, price: 1 })

// 失效查询1:只使用price条件
db.products.find({ price: { $gt: 100 } }) // 无法使用复合索引

// 失效查询2:条件顺序不对
db.products.find({ price: { $lt: 1000 }, category: "电子产品" }) // 不符合最左前缀原则

这种情况就像是你知道书的ISBN号,但图书馆目录是按作者名字排序的,自然找不到。

2. 使用了索引不支持的运算符

// 有效查询:使用等值查询
db.products.find({ category: "电子产品" }) // 可以使用索引

// 失效查询:使用正则表达式
db.products.find({ category: /电子/ }) // 无法使用索引

正则查询就像是要找所有书名包含"编程"的书,但目录只记录了完整书名,自然没法快速查找。

3. 索引选择性太低

// 不好的索引:status字段只有几个可能值
db.orders.createIndex({ status: 1 }) 

// 好的索引:组合高选择性字段
db.orders.createIndex({ userId: 1, createTime: -1 })

这就像是在图书馆里给"中文书"这个分类建索引,结果还是得翻遍半个图书馆。

三、如何诊断索引问题

1. 使用explain()分析查询

// 分析查询执行计划
db.products.find({ category: "电子产品", price: { $gt: 500 } })
    .explain("executionStats")
    
// 重点关注:
// - stage: "IXSCAN"表示使用了索引
// - executionTimeMillis: 执行时间
// - totalKeysExamined: 检查的索引键数量

2. 监控慢查询

// 启用慢查询日志
db.setProfilingLevel(1, { slowms: 100 })

// 查看慢查询记录
db.system.profile.find().sort({ ts: -1 }).limit(10)

四、优化索引的实用技巧

1. 创建合适的复合索引

// 好的复合索引示例
db.products.createIndex({ 
    category: 1,       // 高频查询字段
    price: 1,          // 范围查询字段
    stock: -1          // 排序字段
}, { background: true }) // 后台创建不影响业务

2. 使用覆盖索引

// 创建包含所有查询字段的索引
db.products.createIndex({
    category: 1,
    price: 1,
    name: 1
})

// 查询只需返回索引字段
db.products.find(
    { category: "电子产品", price: { $gt: 500 } },
    { _id: 0, category: 1, price: 1, name: 1 }
)

3. 定期维护索引

// 查看索引使用情况
db.products.aggregate([ { $indexStats: { } } ])

// 删除无用索引
db.products.dropIndex("category_1_price_1")

五、特殊场景的索引优化

1. 全文搜索场景

// 创建文本索引
db.products.createIndex({ 
    name: "text",
    description: "text"
})

// 文本搜索查询
db.products.find({
    $text: { $search: "智能手机 大屏" }
})

2. 地理空间查询

// 创建2dsphere索引
db.stores.createIndex({ location: "2dsphere" })

// 附近查询
db.stores.find({
    location: {
        $near: {
            $geometry: {
                type: "Point",
                coordinates: [116.404, 39.915]
            },
            $maxDistance: 1000
        }
    }
})

六、索引优化的注意事项

  1. 索引不是越多越好:每个索引都会占用内存和磁盘空间,写入时还需要维护索引
  2. 注意内存使用:确保索引能放入内存,否则性能会急剧下降
  3. 测试是关键:任何索引变更都要在测试环境验证
  4. 监控是必须的:建立索引性能基线,持续监控变化

七、实战案例分享

最近帮一个社交应用优化了他们的消息系统。原来的查询是这样的:

// 原始低效查询
db.messages.find({
    receiverId: "user123",
    status: "unread",
    createTime: { $gt: ISODate("2023-01-01") }
}).sort({ createTime: -1 }).limit(50)

优化步骤:

  1. 分析发现没有合适的复合索引
  2. 创建了{ receiverId: 1, status: 1, createTime: -1 }索引
  3. 确保查询使用覆盖索引
  4. 查询时间从1200ms降到25ms

八、总结

索引优化是个细致活,需要结合业务场景和数据特点。记住几个原则:

  1. 理解你的查询模式
  2. 遵循最左前缀原则
  3. 优先考虑高选择性字段
  4. 定期审查和清理无用索引
  5. 测试、测试、再测试