1. MongoDB索引创建与优化实战第一次接触MongoDB索引时我犯了个典型错误——在百万级数据的集合上直接创建索引结果导致生产环境查询响应时间飙升。这个教训让我深刻认识到索引虽好但使用不当反而会成为性能杀手。1.1 七种索引类型详解MongoDB提供了丰富的索引类型每种都有其独特的应用场景单字段索引最基础的索引类型// 创建升序索引 db.products.createIndex({ price: 1 }) // 创建降序索引 db.products.createIndex({ price: -1 })复合索引多个字段组合的索引// 商品价格库存的复合索引 db.products.createIndex({ price: 1, stock: -1 })多键索引针对数组字段的特殊索引// 为tags数组创建索引 db.products.createIndex({ tags: 1 })地理空间索引支持位置查询// 2dsphere索引用于地理空间数据 db.stores.createIndex({ location: 2dsphere })文本索引全文搜索利器// 商品描述全文索引 db.products.createIndex({ description: text })哈希索引均匀分布的数据分片// 用户ID哈希索引 db.users.createIndex({ _id: hashed })TTL索引自动过期数据// 30天后自动删除的会话索引 db.sessions.createIndex({ createdAt: 1 }, { expireAfterSeconds: 2592000 })1.2 索引优化实战技巧在实际项目中我发现这些优化策略特别有效覆盖索引查询当查询只需要返回索引字段时可以避免回表操作// 创建覆盖索引 db.orders.createIndex({ customer_id: 1, order_date: 1 }) // 使用覆盖索引的查询 db.orders.find( { customer_id: user123, order_date: { $gt: ISODate(2023-01-01) } }, { _id: 0, customer_id: 1, order_date: 1 } )索引选择性优化高基数字段更适合建索引。比如手机号字段比性别字段更适合建立索引因为它的唯一性更高。索引合并策略MongoDB可以自动合并多个索引// 查询优化器可能合并这两个索引 db.users.createIndex({ age: 1 }) db.users.createIndex({ status: 1 }) // 查询可能使用索引合并 db.users.find({ age: { $gt: 18 }, status: active })索引大小监控定期检查索引大小很重要// 查看集合索引统计信息 db.products.stats()2. 查询性能深度优化记得有一次排查线上慢查询发现一个看似简单的find操作竟然扫描了上百万文档。通过explain()分析才意识到缺少合适的索引。这个经历让我养成了优化查询必看执行计划的习惯。2.1 explain()方法实战explain()是查询优化的瑞士军刀// 获取查询执行计划 db.orders.find({ total: { $gt: 100 } }).explain(executionStats)关键指标解读executionTimeMillis查询总耗时totalKeysExamined检查的索引键数量totalDocsExamined扫描的文档数量stage查询阶段COLLSCAN最需警惕2.2 聚合管道优化聚合查询是MongoDB的强项但容易性能陷阱管道顺序优化// 不推荐的顺序先处理大量数据再过滤 db.sales.aggregate([ { $project: { item: 1, price: 1 } }, { $match: { price: { $gt: 100 } } } ]) // 推荐的顺序先过滤减少数据处理量 db.sales.aggregate([ { $match: { price: { $gt: 100 } } }, { $project: { item: 1, price: 1 } } ])内存限制处理// 允许聚合使用更多内存 db.sales.aggregate([ { $match: { date: { $gt: ISODate(2023-01-01) } } }, { $group: { _id: $product, total: { $sum: $amount } } } ], { allowDiskUse: true })3. 副本集高可用架构去年我们经历了次机房断电正是MongoDB副本集让系统在30秒内自动恢复。这种亲身经历让我真正理解了副本集的价值。3.1 副本集配置详解标准的三节点副本集配置// 初始化副本集配置 rs.initiate({ _id: myReplicaSet, members: [ { _id: 0, host: mongo1:27017, priority: 2 }, { _id: 1, host: mongo2:27017, priority: 1 }, { _id: 2, host: mongo3:27017, arbiterOnly: true } ] })读写关注级别// 确保写操作传播到多数节点 db.products.insert( { name: 新品, price: 99 }, { writeConcern: { w: majority, j: true } } ) // 从副本节点读取最新数据 db.products.find().readConcern(linearizable)3.2 故障转移实战模拟主节点故障测试# 连接到主节点 mongo --host mongo1 # 主动降级主节点 rs.stepDown(60)关键监控命令// 查看副本集状态 rs.status() // 检查复制延迟 db.printSlaveReplicationInfo() // 查看oplog状态 db.getReplicationInfo()4. 分片集群架构设计处理电商平台的海量订单数据时分片集群是我们的救命稻草。但分片策略的选择让我踩了不少坑特别是热key导致的数据倾斜问题。4.1 分片策略选择哈希分片均匀分布但范围查询效率低sh.shardCollection(ecommerce.orders, { _id: hashed })范围分片适合范围查询但可能数据不均sh.shardCollection(ecommerce.products, { category: 1, price: 1 })混合分片策略结合两者优势// 按日期范围分片再按用户ID哈希分片 sh.shardCollection(logs.entries, { date: 1, user_id: hashed })4.2 分片集群运维平衡器控制// 临时禁用平衡器 sh.stopBalancer() // 设置平衡器窗口期 use config db.settings.update( { _id: balancer }, { $set: { activeWindow: { start: 23:00, stop: 04:00 } } }, { upsert: true } )分片扩容操作# 添加新分片 sh.addShard(shard3/mongo-shard3-1:27017,mongo-shard3-2:27017) # 查看分片状态 sh.status()热点分片处理// 手动迁移chunk sh.moveChunk(ecommerce.orders, { _id: MinKey }, shard2) // 分割大chunk sh.splitAt(ecommerce.orders, { _id: ObjectId(5f3d8a9b8c3a2b1c0d9e8f7a) })