阿里云主机折上折
  • 微信号
您当前的位置:网站首页 > 单字段索引与复合索引

单字段索引与复合索引

作者:陈川 阅读数:39564人阅读 分类: MongoDB

单字段索引

单字段索引是MongoDB中最基础的索引类型,它只针对集合中的单个字段建立索引结构。当查询条件中只包含该字段时,这种索引能显著提高查询性能。

// 创建单字段索引示例
db.users.createIndex({ username: 1 })

这个索引会按照username字段的值进行排序存储,1表示升序,-1表示降序。对于等值查询和范围查询都有效:

// 等值查询
db.users.find({ username: "john_doe" })

// 范围查询
db.users.find({ username: { $gt: "a", $lt: "m" } })

单字段索引的特点:

  • 创建和维护成本低
  • 适合频繁作为查询条件的字段
  • 支持排序操作
  • 可以显著减少集合扫描

复合索引

复合索引是建立在多个字段上的索引,字段顺序对索引效率有重大影响。MongoDB会按照索引字段的顺序存储数据。

// 创建复合索引示例
db.orders.createIndex({ customerId: 1, orderDate: -1 })

复合索引遵循"最左前缀"原则,这意味着查询条件必须包含索引最左边的字段才能使用该索引:

// 能使用索引的查询
db.orders.find({ customerId: "12345" })
db.orders.find({ customerId: "12345", orderDate: { $lt: ISODate("2023-01-01") } })

// 不能使用索引的查询
db.orders.find({ orderDate: { $lt: ISODate("2023-01-01") } })

复合索引的优势:

  • 支持多字段联合查询
  • 可以覆盖更多查询模式
  • 能同时满足查询和排序需求
  • 减少多个单字段索引的开销

索引选择策略

选择单字段还是复合索引取决于查询模式。对于简单查询,单字段索引足够;复杂查询则需要复合索引。

// 查询分析示例
db.products.find({
  category: "electronics",
  price: { $lt: 1000 },
  rating: { $gt: 4 }
}).sort({ createdAt: -1 })

// 最佳索引可能是
db.products.createIndex({ 
  category: 1, 
  price: 1, 
  rating: 1, 
  createdAt: -1 
})

需要考虑的因素:

  1. 查询频率高的字段
  2. 字段的选择性(高基数字段优先)
  3. 排序需求
  4. 内存限制
  5. 写入性能影响

索引优化技巧

实际应用中,可以通过一些技巧优化索引性能:

  1. 使用覆盖查询避免回表:
// 创建覆盖索引
db.customers.createIndex({ email: 1, name: 1 })

// 查询只返回索引字段
db.customers.find({ email: "user@example.com" }, { _id: 0, email: 1, name: 1 })
  1. 索引交集优化:
// 两个单字段索引可能比一个复合索引更高效的情况
db.logs.createIndex({ userId: 1 })
db.logs.createIndex({ action: 1 })

// 查询可能使用索引交集
db.logs.find({ userId: "123", action: "login" })
  1. 部分索引减少索引大小:
// 只为活跃用户创建索引
db.users.createIndex(
  { username: 1 },
  { partialFilterExpression: { isActive: true } }
)

常见问题与解决方案

  1. 索引大小膨胀:
  • 使用TTL索引自动清理过期数据
  • 考虑使用部分索引
  • 定期重建索引
  1. 写入性能下降:
  • 评估索引的实际使用率
  • 删除未使用的索引
  • 在低峰期批量创建索引
  1. 内存不足:
  • 优先保证常用查询的索引在内存中
  • 使用压缩存储引擎
  • 考虑分片集群分散负载
// 监控索引使用情况
db.collection.aggregate([{
  $indexStats: {}
}])

实际案例分析

电商平台产品查询优化:

// 原始查询
db.products.find({
  category: "phones",
  brand: "Apple",
  price: { $gte: 500, $lte: 1000 },
  inStock: true
}).sort({ popularity: -1 }).limit(20)

// 优化后的索引
db.products.createIndex({
  category: 1,
  brand: 1,
  price: 1,
  inStock: 1,
  popularity: -1
})

// 进一步优化为部分索引
db.products.createIndex({
  category: 1,
  brand: 1,
  price: 1,
  popularity: -1
}, {
  partialFilterExpression: { inStock: true }
})

日志系统查询优化:

// 日志查询模式
db.logs.find({
  app: "payment",
  level: "error",
  timestamp: { $gte: ISODate("2023-01-01"), $lte: ISODate("2023-01-02") }
}).sort({ timestamp: -1 })

// 最佳索引设计
db.logs.createIndex({ 
  app: 1,
  level: 1,
  timestamp: -1 
})

// 考虑分片策略
sh.shardCollection("db.logs", { app: 1, timestamp: -1 })

本站部分内容来自互联网,一切版权均归源网站或源作者所有。

如果侵犯了你的权益请来信告知我们删除。邮箱:cc@cccx.cn

前端川

前端川,陈川的代码茶馆🍵,专治各种不服的Bug退散符💻,日常贩卖秃头警告级的开发心得🛠️,附赠一行代码笑十年的摸鱼宝典🐟,偶尔掉落咖啡杯里泡开的像素级浪漫☕。‌