wiredtiger的copy-on-write机制是碎片根源,更新时在新位置写入文档并标记旧版本为过期,滞留page中形成逻辑与物理碎片;compact无法在事务活跃时执行,且单条updateone也会产生碎片,事务仅放大问题。

WiredTiger 的 copy-on-write 机制是根源
不是“更新”这个动作本身有问题,而是 WiredTiger 引擎默认采用 copy-on-write + MVCC 策略处理更新。即使你只改一个字段,引擎也常常不就地覆盖(update-in-place),而是在新位置写入整个文档,并把旧版本标记为“过期”。这些过期数据不会立刻擦除,而是滞留在 WT page 中,等待后台 eviction 或手动 compact 清理。
常见错误现象包括:
- 磁盘空间没明显增长,但
db.collection.stats().wiredTiger["block-manager"]["file bytes available for reuse"]值持续变大 - 写放大严重:监控到
bytes written from cache/storageSize()比值 > 3 - 查询变慢,且
cache overflow count非零,说明 page 缓存压力已传导到查询层
事务会加剧碎片堆积,但不是必要条件
单条 updateOne 也会产生碎片,事务只是放大了问题——它把多个更新打包进一个原子上下文,更容易触发批量 page 写入和版本堆积。尤其当事务内频繁更新同一类文档(比如按时间戳递增的订单状态),page 分裂更频繁,小块空闲空间就散落在各处。
关键限制在于:compact 无法在事务活跃时运行。执行 db.runCommand({compact: "coll"}) 会阻塞所有写操作,包括事务中的写;若此时有长事务未提交,compact 就会 hang 住,日志里反复出现 waiting for write lock。
分片集合的碎片表现更隐蔽
在分片集群中,“碎片”常被误认为是 chunk 数量多,其实真正影响性能的是每个分片内部 WiredTiger 层的物理碎片。MongoDB 7.0+ 已默认启用自动 chunk 合并,但该机制对 WT page 级碎片无效。如果你用 defragmentCollection 整理分片集合,它只合并逻辑 chunk,不清理底层 page 空洞。
注意两个易忽略点:
-
defragmentCollection会清空 chunk 放置历史,导致快照读取或事务可能短暂失败,报错类似stale chunk history - 整理期间元数据更新密集,建议配合
chunkDefragmentationThrottlingMS限流,避免拖垮整个集群的 CRUD 延迟
真正有效的缓解手段不是 compact,而是控制更新模式
靠定期 compact 治标不治本,而且生产环境很难找到安全窗口。更实际的做法是收敛更新行为:
- 优先用单文档原子性替代多文档事务:例如把“扣库存 + 写流水”合并成一个嵌入式文档,用一次
updateOne完成 - 避免高频小字段更新:把状态变更聚合为批量操作,或引入 TTL 索引让旧版本自然过期
- 对写密集场景,考虑哈希分片 + 随机 _id,减少 page 分裂集中度
- 监控必须落到具体指标:别只看磁盘使用率,盯紧
file bytes available for reuse和cache overflow count
碎片本身不可怕,可怕的是它和长事务、低效 Schema、盲目分片叠加后,让问题变得既难定位又难收敛。











