外观
MongoDB 索引最佳实践
索引设计原则
1. 理解查询模式
在设计索引之前,必须深入了解应用程序的查询模式:
- 分析最频繁执行的查询
- 查看查询条件(filter)和排序(sort)字段
- 检查聚合查询的分组和匹配条件
- 识别复合查询的字段组合
2. 遵循 ESR 原则
对于复合索引,遵循 ESR(Equality, Sort, Range)原则:
- Equality:首先添加等值匹配字段(如
field: value) - Sort:然后添加排序字段
- Range:最后添加范围查询字段(如
field > value)
示例
javascript
// 查询模式
db.collection.find({ status: "active", category: "A" }).sort({ created_at: -1 }).limit(10)
// 最优复合索引
db.collection.createIndex({ status: 1, category: 1, created_at: -1 })3. 考虑选择性
选择选择性高的字段作为索引前缀:
- 选择性 = 不同值的数量 / 文档总数
- 高选择性字段(如 user_id、email)适合作为索引前缀
- 低选择性字段(如 status、type)不适合作为索引前缀
4. 避免过度索引
- 每个索引都会增加写入操作的开销
- 权衡查询性能提升与写入性能影响
- 删除不使用或很少使用的索引
索引创建最佳实践
1. 在线创建索引
在生产环境中,使用 background: true 选项在线创建索引:
javascript
db.collection.createIndex({ field: 1 }, { background: true })2. 使用复合索引替代多个单字段索引
对于经常一起查询的字段,创建复合索引比多个单字段索引更高效:
javascript
// 不推荐:多个单字段索引
db.collection.createIndex({ field1: 1 })
db.collection.createIndex({ field2: 1 })
// 推荐:复合索引
db.collection.createIndex({ field1: 1, field2: 1 })3. 考虑索引大小
- 索引越大,内存占用越高,性能越差
- 避免在大型数组或文本字段上创建索引
- 考虑使用部分索引只索引特定文档
4. 使用部分索引
对于特定查询模式,使用部分索引只索引满足条件的文档:
javascript
// 只索引 status 为 active 的文档
db.collection.createIndex(
{ field: 1 },
{ partialFilterExpression: { status: "active" } }
)5. 使用稀疏索引
对于包含 null 值较多的字段,使用稀疏索引:
javascript
db.collection.createIndex({ field: 1 }, { sparse: true })索引维护最佳实践
1. 定期监控索引使用情况
使用 db.collection.aggregate([{ $indexStats: {} }]) 监控索引使用情况:
javascript
// 查看索引使用统计
db.collection.aggregate([
{ $indexStats: {} },
{ $sort: { accesses: -1 } }
])2. 重建碎片化索引
当索引碎片化严重时,重建索引以提高性能:
javascript
// 重建索引
db.collection.reIndex()
// 或删除并重新创建
db.collection.dropIndex({ field: 1 })
db.collection.createIndex({ field: 1 }, { background: true })3. 监控索引大小
定期检查索引大小,及时优化过大的索引:
javascript
// 查看集合和索引统计信息
db.collection.stats()4. 定期审查索引
- 至少每季度审查一次索引
- 删除未使用的索引
- 优化低效索引
- 更新不适合当前查询模式的索引
查询优化最佳实践
1. 使用 explain() 分析查询
使用 explain() 方法分析查询执行计划:
javascript
// 分析查询执行计划
db.collection.find({ field: "value" }).sort({ created_at: -1 }).explain("executionStats")2. 确保查询使用索引
- 避免全表扫描(COLLSCAN)
- 确保查询条件与索引前缀匹配
- 避免在索引字段上使用函数
3. 优化排序操作
- 确保排序字段在索引中
- 避免混合排序方向(如 { field1: 1, field2: -1 })
- 限制排序结果集大小
4. 优化范围查询
- 将范围查询字段放在索引末尾
- 避免在范围查询字段上使用排序
- 限制范围查询结果集大小
特定场景最佳实践
1. 文本搜索
- 使用文本索引进行全文搜索
- 限制文本索引字段数量
- 优化文本搜索查询
javascript
// 创建文本索引
db.collection.createIndex({ title: "text", content: "text" })
// 优化的文本搜索
db.collection.find({ $text: { $search: "keyword" } }).limit(10)2. 地理空间查询
- 根据查询类型选择合适的地理空间索引:
- 2d 索引:平面地理数据
- 2dsphere 索引:球形地理数据
- 限制地理空间查询结果集大小
3. 时间序列数据
- 使用复合索引:
- 考虑使用 TTL 索引自动过期旧数据
- 按时间范围分区数据
javascript
// TTL 索引:7天后自动删除
db.collection.createIndex({ created_at: 1 }, { expireAfterSeconds: 604800 })4. 分片集群索引
- 在所有分片上创建相同的索引
- 考虑分片键与查询字段的关系
- 避免在分片键上创建过多索引
索引性能监控
1. 关键指标
监控以下索引相关指标:
- 索引命中率(Index Hit Ratio)
- 全表扫描次数(COLLSCAN)
- 索引扫描次数(IXSCAN)
- 慢查询次数
- 索引大小增长趋势
2. 监控工具
- MongoDB Atlas 内置监控
- Prometheus + Grafana
- Datadog
- New Relic
常见索引问题及解决方案
1. 索引未被使用
问题:创建了索引,但查询未使用该索引
解决方案:
- 检查查询条件是否与索引前缀匹配
- 确保索引类型与查询类型匹配
- 分析查询执行计划,查看是否存在更优索引
2. 索引过大
问题:索引大小超过预期,占用过多内存
解决方案:
- 考虑使用部分索引
- 优化复合索引字段顺序
- 删除不必要的索引字段
- 重建碎片化索引
3. 写入性能下降
问题:索引过多导致写入性能下降
解决方案:
- 删除不使用的索引
- 合并多个单字段索引为复合索引
- 考虑使用稀疏索引或部分索引
版本差异
MongoDB 4.2+ 特性
- 支持在线创建和删除索引
- 支持索引构建过程中的取消操作
- 改进了复合索引的内存使用
MongoDB 5.0+ 特性
- 支持隐藏索引(hidden: true),用于测试新索引
- 改进了索引统计信息收集
- 支持更灵活的部分索引
MongoDB 6.0+ 特性
- 支持向量索引(用于AI/ML应用)
- 改进了索引构建的并行性
- 支持索引压缩
常见问题(FAQ)
Q1: 如何确定哪些索引需要删除?
A1: 使用以下方法确定需要删除的索引:
- 使用
$indexStats聚合查看索引使用情况 - 分析慢查询日志,识别未使用的索引
- 检查索引大小和写入性能影响
- 删除创建后从未使用过的索引
Q2: 复合索引的字段顺序如何影响查询性能?
A2: 复合索引的字段顺序对查询性能有显著影响:
- 索引前缀匹配原则:查询必须从索引的第一个字段开始匹配
- 高选择性字段放在前面可以更快地缩小结果集
- 排序字段必须与索引顺序一致才能使用索引排序
Q3: 什么时候应该使用稀疏索引?
A3: 稀疏索引适用于以下场景:
- 字段只存在于部分文档中
- 字段包含大量null值
- 只需要查询包含该字段的文档
Q4: 如何优化包含多个条件的查询?
A4: 优化包含多个条件的查询:
- 分析查询条件的选择性,选择最具选择性的字段作为索引前缀
- 遵循ESR原则设计复合索引
- 考虑使用部分索引过滤掉不需要的文档
- 使用explain()分析不同索引的性能
Q5: 如何处理大集合的索引创建?
A5: 处理大集合的索引创建:
- 使用background选项在线创建索引
- 在低峰期创建索引
- 监控索引创建过程的资源使用
- 考虑使用滚动升级方式创建索引
Q6: 索引对写入性能的影响有多大?
A6: 索引对写入性能的影响取决于:
- 索引数量:每个索引都会增加写入开销
- 索引大小:较大的索引写入开销更大
- 文档大小:大型文档的索引写入开销更大
- 写入频率:高写入频率下索引影响更明显
Q7: 如何监控索引使用情况?
A7: 监控索引使用情况的方法:
- 使用
$indexStats聚合查看索引访问统计 - 分析MongoDB日志中的慢查询
- 使用监控工具(如Atlas、Prometheus)查看索引命中率
- 定期运行
db.collection.explain("executionStats")分析查询
Q8: 什么时候应该重建索引?
A8: 以下情况应该重建索引:
- 索引碎片化严重
- 索引大小异常增长
- 查询性能下降
- 集合经历了大量的删除操作
- 升级MongoDB版本后
Q9: 如何优化排序操作?
A9: 优化排序操作:
- 确保排序字段在索引中
- 排序方向与索引方向一致
- 避免在大型结果集上排序
- 考虑使用覆盖索引
Q10: 覆盖索引的优势是什么?
A10: 覆盖索引的优势:
- 查询可以完全从索引中获取数据,不需要回表
- 减少磁盘I/O,提高查询性能
- 适合频繁执行的聚合查询
javascript
// 覆盖索引示例:查询只需要id和name字段
db.collection.createIndex({ field: 1, id: 1, name: 1 })
db.collection.find({ field: "value" }, { id: 1, name: 1, _id: 0 })