不能直接在历史表上建全量索引,因为会拖慢写入、挤占WiredTiger缓存、导致冷数据扫描过多;应使用partialFilterExpression创建热数据专属索引,并通过滚动建索引安全更新时间窗口。

为什么不能直接在历史表上建全量索引
MongoDB 历史表一旦数据量上亿,created_at 或 updated_at 全量索引会显著拖慢写入,且热数据查询时仍要扫描大量冷数据块。更关键的是:索引本身也占内存,冷数据索引页长期不被访问,反而挤占热数据的 WiredTiger cache。
- 冷数据(比如 90 天前)几乎只读、极少参与查询条件
- 热数据(最近 7–30 天)承载 95%+ 的
find、updateOne 和聚合操作
- 全量索引让
explain("executionStats") 显示 totalDocsExamined 居高不下,即使实际匹配数很少
用 partialIndexFilter 实现热数据专属索引
MongoDB 3.2+ 支持 partialFilterExpression,本质是“带条件的索引”。它只对满足表达式的文档建索引项,不存冷数据条目,从源头减小索引体积和维护开销。
- 必须确保查询条件与索引过滤器逻辑一致,否则索引不会被选中
- 推荐用
ISODate 而非字符串比较,避免时区/格式隐式转换失效
- 过滤字段必须出现在查询谓词中(如
{ status: "active", created_at: { $gt: ISODate("2024-06-01") } } 才能命中 { status: 1, created_at: 1 } + partialFilterExpression: { created_at: { $gt: ISODate("2024-06-01") } })
find、updateOne 和聚合操作explain("executionStats") 显示 totalDocsExamined 居高不下,即使实际匹配数很少partialFilterExpression,本质是“带条件的索引”。它只对满足表达式的文档建索引项,不存冷数据条目,从源头减小索引体积和维护开销。
- 必须确保查询条件与索引过滤器逻辑一致,否则索引不会被选中
- 推荐用
ISODate而非字符串比较,避免时区/格式隐式转换失效 - 过滤字段必须出现在查询谓词中(如
{ status: "active", created_at: { $gt: ISODate("2024-06-01") } }才能命中{ status: 1, created_at: 1 }+partialFilterExpression: { created_at: { $gt: ISODate("2024-06-01") } })
示例建索引命令:
db.orders.createIndex(
{ "status": 1, "created_at": -1 },
{
"partialFilterExpression": {
"created_at": { "$gt": { "$date": "2024-06-01T00:00:00Z" } }
}
}
)如何安全更新热窗口边界
热数据时间窗口不是一成不变的。每天滚动推进时,若直接删旧索引+建新索引,会导致短暂无索引状态;而原地修改 partialFilterExpression 不被支持。
- 永远不要
dropIndex 当前正在服务的热索引
- 正确做法:提前一天建好下个窗口的索引(如今天建
created_at > ISODate("2024-06-02")),等生效后,再删掉过期索引(如 created_at > ISODate("2024-05-26"))
- 用
db.orders.getIndexes() 和 db.orders.find().hint(...).explain() 验证新索引是否被真实选用
- 注意 TTL 索引和 partial 索引互斥:不能在同一字段上同时用
expireAfterSeconds 和 partialFilterExpression
冷数据查得慢?别硬扛,换查法
部分索引只加速“热数据命中”的查询。一旦业务真要查冷数据(比如审计、补单),走全表扫描或冷数据专用集合更合理。
- 不要在热索引上加
$or 包含冷条件(如 { $or: [ { created_at: { $gt: ... } }, { created_at: { $lt: ... } } ] }),这会让优化器弃用 partial 索引
- 冷数据查询应走独立集合(如
orders_archive)+ 单独建低频索引,或用物化视图($merge 到汇总集合)预计算
- 如果必须查混合时间范围,优先考虑应用层分拆:热段走主集合 + partial 索引,冷段走归档集合 +
collation: { locale: "en" } 等轻量优化
dropIndex 当前正在服务的热索引 created_at > ISODate("2024-06-02")),等生效后,再删掉过期索引(如 created_at > ISODate("2024-05-26")) db.orders.getIndexes() 和 db.orders.find().hint(...).explain() 验证新索引是否被真实选用 expireAfterSeconds 和 partialFilterExpression
- 不要在热索引上加
$or包含冷条件(如{ $or: [ { created_at: { $gt: ... } }, { created_at: { $lt: ... } } ] }),这会让优化器弃用 partial 索引 - 冷数据查询应走独立集合(如
orders_archive)+ 单独建低频索引,或用物化视图($merge到汇总集合)预计算 - 如果必须查混合时间范围,优先考虑应用层分拆:热段走主集合 + partial 索引,冷段走归档集合 +
collation: { locale: "en" }等轻量优化
时间窗口的粒度、索引字段顺序、是否包含 _id 在复合索引里——这些细节不显眼,但动一个就可能让 executionStats.nReturned 翻倍或归零。










