MongoDB索引优化与createIndex使用详解 1. 为什么需要为非主键字段建立索引在日常数据库操作中我们经常会遇到这样的场景明明查询条件中包含了字段但查询速度依然很慢。这时候为非主键字段建立索引就显得尤为重要。主键索引虽然能保证数据的唯一性但在实际业务中我们往往需要根据其他字段进行快速查询。以电商系统为例商品表的主键可能是商品ID但用户最常查询的却是商品名称、分类、价格等字段。如果没有为这些字段建立索引每次查询都需要全表扫描当数据量达到百万级时查询性能就会急剧下降。注意索引虽然能提高查询速度但也会增加写入时的开销。每次插入、更新或删除数据时数据库都需要维护索引结构。因此索引不是越多越好需要根据实际查询需求合理设计。2. createIndex的基本语法与使用场景createIndex是MongoDB中用于创建索引的核心命令其基本语法如下db.collection.createIndex(keys, options)其中keys指定要建立索引的字段及其排序方式1为升序-1为降序options可选参数用于设置索引的额外属性2.1 单字段索引创建最简单的索引形式是针对单个字段建立索引// 为users集合的age字段创建升序索引 db.users.createIndex({age: 1})这种索引适合经常需要按某个特定字段查询的场景比如按照用户年龄筛选。2.2 多字段组合索引当查询条件经常包含多个字段时组合索引往往更高效// 为products集合创建name和price的组合索引 db.products.createIndex({name: 1, price: 1})组合索引的字段顺序非常重要应该遵循最左前缀原则。也就是说查询条件必须包含索引的最左侧字段才能利用该索引。3. 索引类型深度解析3.1 唯一索引确保索引字段的值在集合中是唯一的// 创建唯一索引 db.users.createIndex({email: 1}, {unique: true})注意如果集合中已有重复数据创建唯一索引会失败。需要先处理重复数据或者使用{unique: true, partialFilterExpression: {field: {$exists: true}}}来只为存在的字段创建唯一约束。3.2 稀疏索引只对存在该字段的文档建立索引// 创建稀疏索引 db.users.createIndex({mobile: 1}, {sparse: true})这种索引适合字段可能为null的情况可以节省存储空间。3.3 TTL索引自动删除过期的文档// 创建TTL索引文档在createdAt后3600秒自动删除 db.logs.createIndex({createdAt: 1}, {expireAfterSeconds: 3600})常用于日志、会话等临时数据的自动清理。4. 索引优化实战技巧4.1 索引选择策略高选择性字段优先字段值越唯一索引效果越好。比如用户ID比性别更适合建索引。查询频率高的字段经常出现在查询条件中的字段应该优先考虑。排序字段考虑如果查询需要排序为排序字段建立索引可以避免内存排序。4.2 索引性能分析使用explain()分析查询执行计划db.products.find({name: 手机, price: {$gt: 2000}}).explain(executionStats)重点关注executionStats.executionTimeMillis查询耗时executionStats.totalKeysExamined检查的索引键数量executionStats.totalDocsExamined检查的文档数量理想情况下totalDocsExamined应该接近返回的文档数而不是全表扫描。4.3 索引维护建议定期监控索引使用情况db.collection.aggregate([{$indexStats: {}}])删除未使用的索引长期不用的索引会增加写入开销重建碎片化严重的索引db.collection.reIndex()5. 常见问题与解决方案5.1 索引不生效的常见原因查询条件不符合最左前缀原则索引{a:1, b:1, c:1}生效查询{a:1},{a:1, b:1}不生效查询{b:1},{c:1}使用了索引不支持的操作符比如正则表达式查询可能无法使用索引数据类型不匹配字段在索引中是数值类型但查询时使用了字符串5.2 联合唯一索引中的NULL值处理这是一个常见陷阱。在MongoDB中如果多个文档的索引字段都为NULL唯一索引会认为它们是重复的可以使用{sparse: true}选项使NULL值不被索引或者使用partialFilterExpression限制索引范围// 方式1稀疏索引 db.collection.createIndex({a:1, b:1}, {unique: true, sparse: true}) // 方式2部分索引 db.collection.createIndex({a:1, b:1}, { unique: true, partialFilterExpression: { a: {$exists: true}, b: {$exists: true} } })5.3 索引大小限制MongoDB的索引有大小限制单个索引条目不能超过1024字节解决大字段索引问题的方法使用字段的子集如前几个字符使用哈希值考虑使用文本索引替代6. 不同数据库系统的索引实现对比6.1 MySQL vs MongoDB索引差异存储结构MySQL主要使用B树MongoDB使用B树覆盖索引MySQL可以通过索引直接获取数据覆盖索引MongoDB即使使用索引也需要访问文档本身多键索引MongoDB支持数组字段的自动展开索引MySQL需要手动处理6.2 特殊索引类型对比全文索引MySQL内置全文索引MongoDB需要专门的text索引地理空间索引两者都支持但语法和功能有差异哈希索引MySQLMemory引擎支持MongoDB支持哈希索引用于分片键7. 实际案例电商系统索引设计假设我们有一个电商系统的产品集合典型查询包括按分类浏览按价格区间筛选按名称搜索按销量排序7.1 初始索引设计// 分类和价格的组合索引 db.products.createIndex({category: 1, price: 1}) // 名称的文本索引 db.products.createIndex({name: text}) // 销量的单字段索引 db.products.createIndex({sales: -1}) // 降序7.2 性能测试与优化通过实际查询测试发现同时按分类、价格和销量查询时效率不高名称的模糊搜索性能较差优化方案// 添加分类价格销量的组合索引 db.products.createIndex({category: 1, price: 1, sales: -1}) // 优化文本索引包含更多字段 db.products.createIndex({ name: text, description: text }, { weights: { name: 3, description: 1 }, name: productTextIndex })7.3 最终索引策略查询索引{category:1, price:1, sales:-1}{category:1, sales:-1, price:1}(不同顺序满足不同查询模式)搜索索引复合文本索引后台维护索引{lastUpdated:1}用于后台数据同步8. 索引监控与维护8.1 监控索引使用情况// 查看索引统计信息 db.products.aggregate([{$indexStats: {}}]) // 查看集合状态 db.products.stats()重点关注accesses.ops索引被查询使用的次数size索引大小usage索引读写比例8.2 索引重建策略当索引碎片化严重时需要考虑重建// 临时解决方案 - 在线重建 db.products.reIndex() // 长期方案 - 在低峰期执行 // 1. 删除旧索引 db.products.dropIndex(old_index_name) // 2. 创建新索引 db.products.createIndex({...})8.3 自动化索引管理对于大型生产系统建议设置索引使用监控告警定期(如每周)分析慢查询建立索引变更评审流程考虑使用MongoDB Atlas的Performance Advisor9. 高级索引技巧9.1 部分索引只为满足特定条件的文档建立索引// 只为价格大于100的商品建立索引 db.products.createIndex( {name: 1}, {partialFilterExpression: {price: {$gt: 100}}} )这样可以大大减少索引大小提高效率。9.2 隐藏索引测试索引效果时可以临时隐藏索引而不删除// 隐藏索引 db.products.hideIndex(name_1) // 取消隐藏 db.products.unhideIndex(name_1)9.3 通配符索引MongoDB 4.2支持通配符索引可以索引未知或动态字段// 为所有子文档的fieldName字段建立索引 db.collection.createIndex({$**: 1}) // 为特定模式的字段建立索引 db.collection.createIndex({metadata.$**: 1})10. 索引与分片的关系在分片集群中索引设计更加复杂10.1 分片键选择分片键决定了数据在集群中的分布应该具有高基数大量不同值保证写操作的均匀分布支持常见的查询模式10.2 索引与分片键分片键自动成为索引只能在分片键上创建唯一索引分片集合的全局唯一索引需要额外考虑10.3 跨分片查询优化尽量避免分散-收集查询考虑使用$or替代$in进行分片键查询合理使用allowDiskUse处理大数据集排序我在实际项目中发现对于大型分片集群定期运行validate命令检查分片均衡性非常重要db.adminCommand({flushRouterConfig: 1}) db.collection.getShardDistribution()