索引膨胀主因是分词策略不当,如edgegram达5x–8x;复合索引字段顺序错误导致扫描效率低;动态映射引发字段爆炸;向量索引需单独评估且不可混入B-tree索引。

索引大小远超预期,先查 expansion factor
索引磁盘占用过大,80% 以上源于文本字段的分词膨胀。比如用 edgegram 分词时,expansion factor 达到 5x–8x,100 万文档、平均文本 200 字节,光索引就可能吃掉 1GB+ 磁盘。
别直接删索引——先确认你用的是哪种分词策略:
-
standard或语言分词:按 1.5x–3x 估算,适合常规搜索 -
keyword:只做整体匹配,膨胀仅 1.1x–1.5x,适合 tag、status 这类枚举值 -
edgegram/ngram:自动补全或模糊匹配才需要,但ngram的mingram=2可能让膨胀达 15x+,必须压测验证
用真实数据跑一次 db.collection.stats(),重点看 indexSize 和 count,再套公式反推实际膨胀系数,比理论值更可靠。
复合索引字段顺序不合理,导致 keysExamined 暴增
慢日志里常见 keysExamined:1498 docsExamined:1498 nreturned:3 ——说明索引扫了 1498 行才捞出 3 条,本质是前导字段选择性太低。
比如建了 {alxxxId: 1, itemTagList: 1},但 alxxxId 值重复率高(如 10 万文档共用 100 个 ID),那索引就从第一个字段开始“筛不干净”,后面字段形同虚设。
调整方向很明确:
- 把高选择性字段放前面:比如
state是 0/1 枚举,alxxxId是长字符串,优先用{state: 1, alxxxId: 1} - 避免在复合索引里塞低选择性字段(如
$in数组、$ne、正则)——它们基本不参与范围裁剪 - 用
db.collection.explain("executionStats")查executionStages.stage是否为IXSCAN,且totalDocsExamined接近nReturned才算高效
动态映射失控,生成海量冗余字段索引
结构不固定的集合开 dynamic mapping,容易触发“映射爆炸”:一个嵌套 JSON 里有 20 个可选字段,每种组合都生成独立字段路径,最终索引字段数破千,RAM 和磁盘全被吃掉。
这不是性能问题,是稳定性风险:
- 禁用动态映射:
db.runCommand({collMod: "collection", validator: {}, validationLevel: "off"})配合严格 schema - 用
sparse: true过滤空值字段,比如discount只在 20% 文档存在,加{sparse: true}后索引体积直降 80% - 对数组字段慎用索引:MongoDB 默认对数组每个元素建索引条目,
itemTagList: {$in: [...]}如果数组平均长度 10,索引条目就翻 10 倍
向量索引没单独评估,和文本索引混在一起算
向量搜索的索引(如 HNSW)和传统 B-tree 索引不是一回事。直接套用 (avg. size of indexed text) 公式会严重低估。
向量索引磁盘开销得单算:
- 原始向量占空间:
total number of vectors × vector dimensions × 4(float32) - HNSW 结构额外开销通常为原始体积的 1.5–3 倍,尤其
ef_construction设太高时 - 别把向量字段和其他字段塞进同一个复合索引——B-tree 根本不支持向量查询,纯属浪费空间
真正要警惕的是:向量索引一旦建错(比如维度错配、未归一化),不仅磁盘涨得快,查询还会 fallback 到全表扫描,CPU 直接拉满。
索引不是越多越好,而是越精准越省资源。最常被忽略的一点:删掉不用的索引后,WiredTiger 不会立刻释放磁盘空间——它得等下一次 checkpoint 和后台清理,期间 disk space used 显示不变,但新写入已走新路径。耐心等 60 秒以上再查 db.collection.stats().indexSize。


















