时序集合默认索引{timefield:1,metafield:1}仅支持时间与元数据整体匹配,无法高效查询测量值或metafield子字段;高频过滤、分组聚合、多条件告警三类场景必须为测量值或metafield子字段显式建复合索引。

直接用 {timeField: 1, metaField: 1} 或默认索引撑不住真实查询压力——时序集合虽自带默认复合索引,但测量值(如 temperature、pressure)参与高频过滤或聚合时,必须显式建索引,否则 explain() 里会看到大量 docsExamined 远高于 nReturned。
为什么时序集合的默认索引不够用
MongoDB 6.3+ 为时间序列集合自动创建 {timeField: 1, metaField: 1} 索引,但它只覆盖时间 + 元数据字段。一旦查询条件含测量值(比如 {temperature: {$gt: 25}}),该索引就完全失效——B-tree 无法跳过前导字段去匹配后置字段。更关键的是,metaField 是嵌套文档,其内部字段(如 sensorId)不被该默认索引“展开”索引,所以 {metaField.sensorId: "A1234"} 也走不上。
- 默认索引不支持对测量值字段的等值/范围查询
-
metaField是对象,不是扁平字段,{metaField: {sensorId: "A1234"}}匹配精度低,且无法利用索引排序 - 聚合管道中若用
$match过滤测量值,没对应索引就会全 collection 扫描
测量值字段必须单独建复合索引的三个典型场景
不是所有测量值都需要索引,但以下三类必须加,否则性能断崖下跌:
-
高频过滤 + 时间范围:如监控告警查
{sensorId: "A1234", temperature: {$gt: 30}, timestamp: {$gte: ...}}→ 建{sensorId: 1, temperature: 1, timestamp: 1},严格按 ESR 顺序(等值→等值→范围) -
按元数据分组 + 测量值聚合:如
db.metrics.aggregate([{$match: {timestamp: {$gte: ...}}}, {$group: {_id: "$metaField.location.city", avgTemp: {$avg: "$temperature"}}}])→ 需{timestamp: 1, "metaField.location.city": 1}支持时间过滤 + 分组字段前置 -
多条件组合告警:如同时查
temperature > 30 AND humidity → 必须建 <code>{temperature: 1, humidity: 1, pressure: 1},且字段顺序按实际查询中出现频率降序排(非固定 ESR,因全是等值)
建索引时最容易踩的四个坑
时序集合对索引更敏感,写放大和内存占用比普通集合更明显:
- 别在
metaField上建通配符索引(如{"metaField.$**": 1}):它会让索引体积暴增,且无法用于精确匹配子字段 - 避免把
timestamp放在复合索引最右位还带$sort:例如{sensorId: 1, timestamp: 1}+.sort({temperature: -1}),排序字段没进索引,必然触发内存排序 - 测量值字段若为浮点(如
temperature: 25.400000000000002),哈希索引不适用,且需确认值精度 ≤ 2⁵³,否则"hashed"索引会出错 - 不要为同一组字段建多个方向组合索引(如同时建
{a:1,b:1}和{a:-1,b:-1}):时序查询几乎全是升序时间,冗余索引只会拖慢写入和增加 cache 压力
真正难的不是建索引,而是判断哪些测量值值得索引——它取决于查询模式是否稳定、数据分布是否偏斜、以及是否愿意为写入延迟换查询响应。一个 temperature 字段,在每秒百万写入的 IoT 场景下加索引,可能让单节点写吞吐掉 30%,这得靠 analyzeShardKey 采样 + explain("executionStats") 看各 shard 的 executionTimeMillisEstimate 分布来实测,不能拍脑袋。











