time series集合仅在timefield上自动创建内部聚集索引,其余字段(如device、sensorid等metafield)必须手动建索引,否则高频查询将全表扫描;该聚集索引不显示在listindexes()中,但支撑时间范围查询与列式压缩。

Time Series集合不会自动创建你期望的查询索引,它只在timeField上建内部聚集索引,其余字段必须手动建索引——否则按设备、标签等常见查询会全表扫描。
为什么listIndexes()看不到timeField索引
Time Series集合底层由隐藏的“bucket集合”支撑,timeField上的聚集索引是内部管理的,不暴露给listIndexes()。你调用db.collection.stats()能看到"timeseries" : { ... }字段,但查不到索引名。
- 这不是bug,是设计:MongoDB靠这个聚集索引实现时间范围快速定位和列式压缩
- 如果你执行
db.collection.find({ ts: { $gt: ISODate("...") } }),它能走这个索引;但db.collection.find({ device: "D1001" })默认不走任何索引 - 不要试图用
createIndex({ ts: 1 })重复建——会报错“cannot create index on time-series collection”
哪些字段必须手动建索引
所有非timeField的高频查询字段,尤其是metaField和业务标签字段,必须显式建索引。
-
metaField(如device、sensorId):按设备查最近10条数据,或聚合前先$match过滤,没索引就扫全量bucket - 复合查询字段(如
{ device: 1, ts: -1 }):适合“每个设备最新一条”这类查询,注意顺序——metaField在前,timeField在后 - 测量值字段(如
temperature):仅当你要做范围筛选({ temperature: { $gt: 30 } })且频次高时才考虑,否则压缩后查询开销反而大
granularity如何影响索引效率
granularity不改变索引结构,但它决定数据在bucket内的物理排布密度,间接影响索引的“有效跨度”。
- 设为
"seconds"而数据实际每分钟一条:bucket内稀疏,metaField索引的每个键指向大量空时间片,查询时跳转成本高 - 设为
"minutes"且数据均匀:一个bucket存60条左右,metaField索引的每个键对应紧凑数据块,$match+$sort延迟下降明显 - 实测:同一批IoT数据,
granularity: "minutes"下device索引的查询QPS比"seconds"高2.3倍
TTL清理依赖索引,但不能只建单字段timeField索引
Time Series集合支持expireAfterSeconds选项,但若只靠timeField索引清理,MongoDB会遍历全部bucket,I/O压力陡增。
- 正确做法:建复合TTL索引,如
db.collection.createIndex({ device: 1, ts: 1 }, { expireAfterSeconds: 2592000 })(30天) - 这样MongoDB能按
device分片清理,每次只处理一个设备的时间段,避免锁表和长事务 - 注意:
expireAfterSeconds必须配合索引字段顺序——timeField必须是复合索引的最后一个字段
最容易被忽略的是:即使用了Time Series集合,metaField索引也得自己建、得选对字段、还得配好顺序;内部聚集索引只管时间轴,不管你的业务维度。











