MongoDB中的固定集合(capped collection)是一种具有固定大小的特殊集合,它在创建时就被分配了确定的存储空间,当集合达到容量上限后,新插入的文档会自动覆盖最早写入的文档。这种机制类似于操作系统的环形缓冲区,能够保证写入性能的稳定,同时避免数据无限膨胀。固定集合在内部以插入顺序维护文档,不支持按照任意字段建立索引来提升随机查询,但天然适合顺序读写密集的场景。

固定集合的底层存储与核心特性
固定集合在创建时通过size参数指定总字节数,MongoDB会一次性向操作系统申请并预分配这部分空间,不会随着文档增加而发生频繁的磁盘文件扩容。这种预分配策略减少了写操作过程中的元数据变更和文件增长开销,使得顺序插入的吞吐量明显高于普通集合。同时,固定集合强制文档的存储顺序与插入顺序一致,也就是说,在底层数据文件中,先写的文档一定排在前面,后写的文档追加在尾部,当尾部空间用尽时,写入指针会绕回到头部覆盖旧文档。
因为存储结构是环形的,固定集合不允许执行删除单条文档的操作,也不支持对已有文档执行导致体积变大的更新。如果你尝试用deleteOne删除其中一条记录,驱动会直接返回错误。另外,固定集合在创建之后不能通过collMod之类命令缩小或扩大容量,只能删除整个集合并使用新的参数重建。它还支持可选的max参数来限制文档数量,当文档数达到上限但字节数未满时,同样会开始覆盖最老文档。
在索引方面,固定集合可以像普通集合一样建立索引,但最常用的还是利用自然顺序($natural)来读取。由于插入顺序即存储顺序,按照{ $natural: 1 }排序的遍历就等价于按时间先后读取,而{ $natural: -1 }则是最新数据优先。对于只关心最近若干条日志或事件的系统,这种读取方式不需要额外的时间字段索引,节省了内存和写入成本。
固定集合与普通集合的差异对比
普通集合采用的是动态增长的命名空间文件,文档可以随意插入到任意有空隙的位置,也允许删除、扩容和更新。这种灵活性带来了随机写和索引维护的成本,在高频日志写入场景下容易产生磁盘碎片和性能抖动。固定集合则放弃了单文档删除与自由更新能力,换取了稳定的写入延迟和可预期的存储占用。对于审计、埋点、消息中转这类只追加、偶尔读取最近数据的业务,固定集合显然更合适。
从查询角度看,普通集合在建立了合适索引后能够高效地进行任意条件检索,而固定集合如果没有额外索引,只能做全集合扫描或自然顺序遍历。不过在典型用法中,固定集合本来就不是为复杂查询设计的,它更多充当高速流水线缓冲。例如,应用把原始请求日志写入固定集合,后台任务定期将其中数据聚合后存入普通集合做长期分析,这样既保住了写入速度,又不影响后续灵活查询。
下面的表格总结了两者在几个关键维度上的区别:
| 维度 | 固定集合 | 普通集合 |
|---|---|---|
| 空间分配 | 创建时固定大小 | 动态增长 |
| 文档删除 | 不支持单条删除 | 支持 |
| 容量调整 | 需删重建 | 自动扩展 |
| 写入性能 | 稳定且高吞吐 | 受碎片与索引影响 |
| 典型场景 | 日志、事件流 | 业务数据、复杂查询 |
可以看到,两者并不是替代关系,而是互补。架构上把高速写入层和持久分析层分开,是很多MongoDB用户验证过的做法。
固定集合的创建、写入与读取实战
创建固定集合必须使用db.createCollection并显式传入capped: true以及size。下面的例子创建了一个最大大小为十万字节、最多容纳一千个文档的固定集合event_log。注意size单位是字节,如果只设了max而没有足够size,MongoDB仍会以字节上限优先触发覆盖。
// 创建固定集合,大小100KB,最多1000条
db.createCollection('event_log', {
capped: true,
size: 100000,
max: 1000
});
// 插入几条事件
db.event_log.insertMany([
{ type: 'click', user: 'a', ts: new Date() },
{ type: 'view', user: 'b', ts: new Date() },
{ type: 'pay', user: 'a', ts: new Date() }
]);
写入操作和普通集合几乎一致,但你要避免执行会让文档变大的更新。例如把一个原本只有两个字段的文档更新成带有大数组的文档,就可能触发错误。读取时最典型的方式是利用$natural顺序,以下代码取出最新写入的5条记录:
// 按插入顺序倒序,取最近5条
db.event_log.find().sort({ $natural: -1 }).limit(5).forEach(doc => {
printjson(doc);
});
// 顺序遍历全部(从最老到最新)
db.event_log.find().sort({ $natural: 1 }).forEach(doc => {
printjson(doc);
});
如果你的应用使用MongoDB驱动而非Shell,逻辑完全相同。以Node.js为例,可以通过createCollection选项传入相同参数,然后用insertOne写数据,用find().sort({ $natural: -1 })读最新。需要特别注意的是,固定集合在副本集里也能正常工作,但覆盖写操作会作为幂等的删除加插入同步到从节点,因此在从节点上读取自然顺序同样能看到滚动效果。掌握了这些创建与读写方式,你就可以在日志收集、设备上报等模块中放心引入固定集合了。
使用固定集合时的常见误区与规避
不少团队在第一次使用固定集合时,会误以为它能像普通表一样通过remove清理垃圾数据,结果在生产环境拿到报错才意识到设计约束。正确思路是把固定集合当作不可变流水,而不是可编辑仓库。如果确实需要根据业务规则丢弃某些数据,应该在消费端处理,而不是试图在原集合内删除。
另一个误区是凭感觉设size,导致容量过小频繁覆盖,或者过大浪费磁盘。建议先统计单条文档平均字节数和期望保留的时长,用公式size = 平均字节 × 每秒写入数 × 保留秒数 × 冗余系数估算。若文档数限制更严格,再配合max。这样既能兜住突发流量,也不会让MongoDB占用过多空间。
还有人给固定集合建了过多索引,以为能兼得写入速度和复杂查询。其实每多一个索引,插入时就要多维护一棵B树,固定集合的写入优势会被削弱。通常保留_id默认索引即可,复杂查询交给下游普通集合或数仓,整体架构反而更清爽。
MongoDBcapped_collection固定集合修改时间:2026-08-13 19:36:42