metafield 应选稳定、低基数、全量存在的字段,如 device 或 stationcode;禁用动态值,须非空且类型一致,不可为嵌套路径或对象;ttl 索引须为 {metafield: 1, timefield: 1} 顺序。

metaField 字段该选什么值
metaField 不是随便挑个字段名就行,它得承载「能区分数据源」的稳定标识。比如 IoT 场景下,device 或 sensorId 是合理选择;气象站数据用 stationCode;金融行情用 symbol + exchange 组合(但注意:metaField 只能是一个字段名,不能是嵌套路径,所以得提前把组合值存成字符串)。
常见错误是拿动态字段当 metaField:比如用 status、temperature 这类随时间剧烈变化的指标——这会让 MongoDB 无法有效分桶,压缩率下降,查询也难走索引。
- 必须所有文档都包含该字段,且类型一致(建议统一用字符串或 ObjectId)
- 字段值应低基数、高稳定性:设备 ID 比“上报时间是否在工作日”更合适
- 避免大小写混用,如
deviceId和deviceid会被视为不同元数据,导致分桶碎片化
metaField 为空或缺失会怎样
如果某条文档没写 metaField 字段,或者写了但值为 null / undefined,MongoDB 会把它归入一个隐式的 “null bucket”,所有这类文档挤在一起。后果很直接:
- 压缩率骤降——null bucket 无法利用列式存储优势
- TTL 清理失效:复合 TTL 索引
{ device: 1, ts: 1 }对 null 值不生效,这批数据永远不被自动删除 - 聚合查询变慢:$group 按
metaField分组时,null 值单独成一组,还可能拖慢并行度
上线前务必加校验:插入前检查 doc[metaField] 是否存在且非空,或用 schema validation 强制约束。
要不要把多个标签塞进一个 metaField
不可以直接塞多个键值对进去。metaField 必须指向一个字段,而这个字段的值可以是对象——但 MongoDB 不会递归解析对象内部结构来优化分桶或索引。例如:
{ "ts": ISODate("..."), "meta": { "site": "A", "room": "B", "sensor_type": "temp" } }
这样写,meta 字段整体被视为一个不可拆解的单元,哪怕你建了 { "meta.site": 1, ts: 1 } 索引,TTL 也不会按 site 粒度清理。
正确做法是二选一:
- 扁平化:用
"device": "A-B-temp"这种拼接字符串,确保唯一性与可读性平衡 - 单一层级:只选最关键的区分维度,比如优先保
device,其他标签走普通字段 + 单独索引(用于过滤,不参与分桶)
别为了“看起来完整”把 metaField 设计成大杂烩,它不是 JSON Schema,而是物理分桶锚点。
metaField 和 TTL 索引怎么配才真正生效
时序集合自带逻辑优化,但 TTL 清理不自动绑定 metaField。你必须手动建复合索引,且顺序不能错:
- 必须是
{ metaField: 1, timeField: 1 },反过来({ timeField: 1, metaField: 1 })会导致按时间全表扫描,失去设备粒度清理能力 - expireAfterSeconds 值要落在业务容忍范围内,比如设备日志保留 30 天就设
2592000,别写成3600(1 小时)还纳闷为啥数据秒删 - 索引创建必须在集合写入大量数据前完成,否则重建代价高;可用
db.sensors.createIndex({ device: 1, ts: 1 }, { expireAfterSeconds: 2592000 })
最容易被忽略的一点:索引建完不等于立刻生效——MongoDB 每分钟轮询一次过期文档,且只清理已提交的旧 bucket。刚插入的数据不会被马上扫掉,这是设计使然,不是 bug。











