零停机迁移需业务代码主动识别多版本文档结构,通过schema_version字段(整型)统一标记、索引、分支读写,并配合渐进式字段迁移与聚合容错处理。

零停机迁移在 MongoDB 中不依赖“自动模式切换”,而是靠业务代码主动识别并兼容多版本文档结构。Schema Versioning 本身不解决写入路径或数据同步问题,它只提供一种安全演进文档形态的机制。
如何在集合中显式标记和读取 schema 版本
关键不是加个字段就完事,而是让所有读写逻辑都感知 version 字段的存在。常见错误是只在插入时写 schema_version,但查询、更新、聚合时完全忽略它,导致新旧逻辑混用出错。
- 统一约定字段名:
schema_version(整型,如 1、2、3),避免用字符串或语义化命名(如 "v2.1")——后者无法做范围判断和索引优化 - 插入时强制写入:
db.users.insertOne({ name: "Alice", email: "a@b.c", schema_version: 2 }) - 读取时必须过滤或分支处理:例如应用层查用户前先判断
schema_version,再决定是否展开profile.address.city还是读city字段 - 为
schema_version建索引:db.users.createIndex({ schema_version: 1 }),否则全量扫描成本高
如何安全执行字段迁移(比如把 email 拆成 email_local/email_domain)
这不是一次性 ALTER TABLE,而是一段带状态控制的渐进式更新。最容易被忽略的是“中间态”文档的处理逻辑——既不是旧结构,也不是新结构,却可能因并发写入真实存在。
- 先上线兼容读逻辑:新代码能同时解析
email: "a@b.c"和{ email_local: "a", email_domain: "b.c" } - 用后台任务分批迁移:
db.users.updateMany({ schema_version: 1, email: { $type: "string" } }, [ { $set: { email_local: { $split: ["$email", "@"] } } } ]),注意$split返回数组,需二次提取 - 迁移过程中禁止直接删旧字段,等所有读逻辑都弃用
email后,再用另一个迁移任务清理 - 监控
schema_version分布比例:通过db.users.aggregate([ { $group: { _id: "$schema_version", count: { $sum: 1 } } } ])确认 v1 文档是否归零
为什么不能只靠 mongodump/mongorestore 实现零停机
因为 dump/restore 是全量快照操作,期间新写入的数据不会被包含。哪怕你用 --oplog 参数,也只保证 dump 结束时刻的 oplog 起点,无法与业务双写或应用层 version 控制对齐。
-
mongodump --oplog生成的 oplog.bson 是 dump 过程中捕获的变更,但无法覆盖 dump 开始前那几秒的写入(即 Oplog 时间窗口漂移) - restore 时若目标库已有同名集合,
mongorestore默认不覆盖,需加--drop,但这会造成服务中断 - 更严重的是:dump 出来的文档不含
schema_version字段,除非你在导出前用$addFields注入,否则版本信息丢失 - 真正零停机场景下,应放弃全量 dump 思路,改用在线双写 + 增量追平(如解析 oplog.rs 并按
ts对齐起点)
Schema Versioning 的复杂点不在定义字段,而在所有 CRUD 操作都要做 version 分支判断;最容易被忽略的是聚合管道中对多版本字段的容错处理——比如 $lookup 关联时,被关联集合的文档可能有不同 schema_version,必须提前 normalize 字段名才能 join。


















