索引吃掉大量wiredtiger缓存内存,因其本身不压缩、字段越长基数越高条目越多则开销越刚性;应验证闲置索引再删除,并优先采用partialfilterexpression、优化字段顺序与类型、使用sparse索引等策略降低内存占用。

为什么索引会吃掉大量内存
WiredTiger 缓存不仅存数据,也存索引——每个索引条目都占缓存空间,且索引越大,加载进缓存的页越多。更关键的是:索引本身不压缩(或压缩率远低于文档),字段越长、基数越高、条目越多,内存开销越刚性。比如一个 {bio: 1} 索引,若 bio 平均 2KB,100 万文档就至少占 2GB 缓存;而同样字段用 text 索引还会额外膨胀。
删掉没用的索引前先确认它真没被用
直接 db.collection.dropIndex() 很快,但容易误伤。必须验证索引是否真正闲置:
- 查慢日志里最近 24 小时的
winningPlan.indexName,看目标索引是否出现 - 运行
db.collection.find({}).explain("executionStats"),观察executionStats.executionStages.inputStage.indexName是否为空或指向其他索引 - 检查
db.collection.getIndexes()输出中,key为{"_id": 1}或全为text的索引,大概率是旧版本遗留冗余项 - 用
db.collection.stats({indexDetails: true})查每个索引的accesses字段(MongoDB 4.2+),值为0或长期无增长才可考虑清理
用 partialFilterExpression 过滤冷数据
部分索引不是“少建几个字段”,而是“少建几条记录”。对时间敏感或状态分明的集合,这是最立竿见影的内存减法:
- 时间范围过滤必须用 BSON
Date类型,不能写字符串:{created_at: {$gt: ISODate("2024-06-01T00:00:00Z")}} -
partialFilterExpression不支持$or、$regex、$text,也不允许引用不存在字段(创建会失败) - 混合条件更安全:比如高频查已支付+近 30 天订单,就用
{status: "paid", created_at: {$gt: ...}},避免单字段选择率低导致索引被跳过 - 建完立刻验证:
db.collection.stats().indexDetails["idx_name"].size看字节数,再用.explain("executionStats")确认totalKeysExamined明显小于全量索引值
调整复合索引字段顺序和类型
字段顺序不是语法习惯,而是内存结构决定的——WiredTiger 按 key 前缀组织 B-tree 页,前置字段的值越稳定、基数越低,页内键值重复率越高,压缩率越好:
- 把高频等值查询字段放最前(如
tenant_id、status),范围查询字段(如created_at)放后面 - 实测显示,将
{status: 1, created_at: -1}改为{created_at: -1, status: 1}可能使索引体积增加 25% 以上 - 数值字段统一类型:避免混用
int32和double,字符串字段提前 trim + 小写化,减少因格式差异导致的键分裂 - 对稀疏字段(如仅 15% 文档含
discount),用sparse: true创建索引,能砍掉 80%+ 条目
索引内存占用不是配置调参就能解决的问题,它根植于数据模式、查询路径和字段语义。最容易被忽略的是:部分索引的 partialFilterExpression 必须与真实查询谓词完全匹配(包括字段存在性),差一个括号或类型,索引就等于没建。











