CentOS 上 MongoDB 索引优化实操指南
一 环境准备与监控基线
- 在 CentOS 上先建立可观测性:开启慢查询日志、使用 mongostat、mongotop 观察操作与 I/O,定位是否存在 COLLSCAN、高 DocsExamined/KeysExamined、高延迟等关键症状。慢日志关注两个核心指标:DocsExamined(扫描文档数)与 KeysExamined(扫描索引键数),二者过大通常意味着需要加索引或改索引。必要时用 db.currentOp() 排查长事务与阻塞操作。
- 检查现有索引规模与使用情况:用 db.collection.getIndexes() 列出索引,用 db.collection.stats() 查看 totalIndexSize,并借助 indexStats 聚合阶段或性能顾问识别“几乎不被使用”的索引,作为清理候选。
二 索引设计方法与落地步骤
- 用 explain(“executionStats”) 验证关键查询是否走索引(stage 为 IXSCAN 而非 COLLSCAN),并核对 nReturned≈DocsExamined/KeysExamined,确保索引真正减少了扫描量。
- 复合索引遵循 ESR 规则(Equality → Sort → Range) 安排字段顺序;等值条件放最左,排序字段居中,范围/非等值放最右。例如:{status:1, createDate:1, totalAmount:-1} 可高效支撑 status 等值过滤 + 按 createDate 排序 + totalAmount 范围/排序的组合查询。
- 优先设计 覆盖查询:将查询所需字段全部放入索引,并在返回结果中排除 _id(或把 _id 也加入索引),使执行计划中的 totalDocsExamined=0,避免“回表”。
- 选择高选择性字段建索引,避免对低区分度字段(如性别)单独建索引;必要时用 复合索引 把低区分度字段与高选择性字段组合,提升过滤效率。
- 典型索引类型与场景:
- 单键索引:单字段高频查询(如 username、email)。
- 复合索引:多条件查询与排序组合(遵循 ESR)。
- 多键索引:数组字段(如 tags:1)。
- 文本索引:全文检索(content:“text”)。
- 地理空间索引:2dsphere(地理位置)。
- 哈希索引:分片键均匀分布({userId:“hashed”})。
- TTL 索引:自动过期({createdAt:1}, {expireAfterSeconds:3600})。
- 通配符索引:4.2+,动态匹配不确定字段({“$**”:1})。
三 常见查询与索引匹配模板
| 查询模式 |
推荐索引顺序 |
说明 |
| find({a:1}).sort(a) |
{a:1} |
同字段升/降序只需一个索引 |
| find({a:1}).sort(b) |
{a:1,b:1} 或 {a:1,b:-1} |
排序字段必须包含在索引中且方向匹配 |
| find({a:1,b:2}).sort© |
{a:1,b:2,c:1} |
等值→排序 |
| find({a:{$gte:1},b:1}).sort© |
{b:1,a:1,c:1} |
等值→排序→范围 |
| find({$or:[{a:1,b:1},{c:1,d:1}]}) |
{a:1,b:1} 与 {c:1,d:1} |
$or 各子句需有独立最优索引 |
| 字符串比较含排序规则 |
索引与查询使用相同 collation |
不同 collation 无法使用该索引做字符串比较 |
| 覆盖查询 |
索引包含查询与返回字段,且 _id:0 |
执行计划应无文档回表 |
| 分片集群覆盖查询 |
索引包含 分片键 |
否则内部仍需访问分片键字段 |
| 上述模板可直接映射到 createIndex 语句,并用 explain 校验是否命中与是否覆盖。 |
|
|
四 维护与常见陷阱
- 删除冗余与低效索引:减少写入放大与内存占用;注意包含关系(如已有 {a:1,b:1,c:1},通常无需再建 {a:1,b:1})、等值唯一前缀(若 a 唯一,{a:1,b:1} 与 {a:1,c:1} 中仅保留 {a:1} 即可)。
- 非等值与 $or 的正确姿势:多字段非等值通常仅最左字段有效;等值应放左,范围放右;$or 必须为每个子句单独建立最优索引,联合一个大索引往往无效。
- 排序与方向:多字段排序必须严格匹配索引方向;同一字段不同方向(1/-1)只需建一个索引即可覆盖两种排序需求。
- 字符串与排序规则:若索引定义了 collation,查询也必须用相同 collation,否则索引无法用于字符串比较。
- 覆盖查询陷阱:返回结果默认包含 _id,如未排除且 _id 不在索引中,就不是覆盖查询;要么把 _id:0,要么把 _id 加入索引。
- 重建索引与碎片:碎片严重时可用 db.collection.reIndex() 重建,但务必在维护窗口进行,避免高峰期影响业务。
- 谨慎使用 hint():仅在验证计划、临时绕行优化器错误等场景使用,避免人为指定导致长期劣化。
五 系统与配置层面的优化
- 存储引擎与缓存:使用 WiredTiger,将 storage.wiredTiger.engineConfig.cacheSizeGB 设为可用内存的约 50%(视业务而定),并启用压缩(如 snappy)降低 I/O 与空间占用。
- I/O 与硬件:优先 SSD 提升索引与数据访问性能;保证足够内存,尽量让热点索引常驻缓存,减少磁盘读。
- 分片场景:为分片键建立索引;需要均衡分布时可选 哈希索引;分片集群做覆盖查询时,索引需包含 分片键。