MongoDB 不自动处理 Schema 变更兼容性,旧数据原样保留,需应用层显式管理 schemaVersion、版本路由及字段迁移。

MongoDB 本身不处理 Schema 变更的兼容性,旧数据不会自动“升级”或被忽略——它原样躺在集合里,读取时由应用层决定怎么解释。
schemaVersion 字段必须显式写入文档
没有内置机制自动给已有文档加 schemaVersion;你得在插入或更新时主动设置。比如用 Laravel-MongoDB 的 HasSchemaVersion trait,它只在模型新建/保存时写入该字段,对历史文档完全无感。
- 新文档:创建时自动带
"schemaVersion": 2 - 旧文档:仍只有
"name": "Alice",没这个字段 - 迁移脚本必须手动补全:用
updateMany({schemaVersion: {$exists: false}}, {$set: {schemaVersion: 1}})
读取时靠应用层做版本路由
不能指望驱动或服务器帮你判断字段含义。Eloquent 模型的 retrieved 事件触发后,才执行 migrateSchema() —— 这意味着裸调 Collection::find() 或 mongosh 直连,压根不走这套逻辑。
- 字段重命名:旧版存
homePhone,新版用contacts.home→ 迁移函数里要写$this->contacts['home'] = $this->getAttribute('homePhone'); - 默认值填充:
$this->galaxy = $this->getAttribute('galaxy') ?? 'Milky Way'; - 类型转换:
price从 string 改成 number →$this->price = (float) $this->getAttribute('price');
聚合管道不感知 schemaVersion
$lookup、$unwind、$group 这些操作只认字段名和结构,不管文档属于哪个版本。如果你在 pipeline 里写了 {$project: {galaxy: 1}},而某些文档根本没有 galaxy 字段,结果就是 null 或缺失 —— 不报错,也不自动 fallback。
- 聚合前必须预处理:要么用
$ifNull显式兜底,如{$ifNull: ["$galaxy", "Milky Way"]} - 要么在 match 阶段过滤掉旧版本:
{$match: {schemaVersion: {$gte: 2}}} - 千万不能依赖 Stable API:它只保证
$lookup行为不变,不保证字段存在
真正容易被忽略的,是那些没走 ORM 层的查询路径——定时任务、后台导出脚本、监控聚合、甚至 DBA 用 mongosh 跑的 ad-hoc 查询,它们绕过所有迁移逻辑,直接暴露字段不一致问题。











