零停机字段变更的核心是双态兼容:新旧字段共存、新旧代码并行,通过访问器fallback、双索引保障、后台渐进清理实现;SchemaBuilder仅管理索引与集合选项,不处理文档结构变更。

MongoDB 本身不强制 Schema,所谓“停机”不是它造成的,而是你用错了方式——把文档数据库当关系库用,又没设计好演进路径。
为什么改个字段还要停机?其实是代码层在硬编码结构
很多人在应用里直接写死 user.name、user.age,一旦新增 user.phone_verified,就去改 Model 类、改验证规则、改前端表单,最后发现旧数据缺失字段,查询报错或渲染异常。这不是 MongoDB 的问题,是你把动态结构当静态契约用了。
- Model 层用
$fillable或schema.pre('save')强制校验字段,等于给无模式加锁 - 前端 JS 直接访问
user.profile.address.city,但老数据里profile是 null,就会炸 - 聚合查询中硬写
{$match: {status: "active"}},而新版本 status 已拆成status_id和status_text字段
热更新必须靠“双态兼容”,不是靠删集合重来
真正零停机的字段变更,核心是让新旧数据共存、新旧代码并行。比如加一个 email_verified 字段:
- 写入时:新代码写
email_verified: true,老代码继续写is_email_confirmed: true(保留旧字段) - 读取时:Model 中定义访问器
getIsEmailVerifiedAttribute(),优先取新字段, fallback 到旧字段 - 索引上:同时建
{email_verified: 1}和{is_email_confirmed: 1},避免查询降级 - 清理时机:等所有服务升级完成、历史数据补全后,再用后台任务逐步删旧字段(不是上线时干)
laravel-mongodb 的 SchemaBuilder 不是用来建表的,是管索引和生命周期的
别被名字骗了——MongoDB 没有 ALTER TABLE,SchemaBuilder 的作用只在两处:
- 创建/删除索引:
$table->index('email')、$table->geoHaystack('location'),这些操作在线执行不锁表 - 集合初始化控制:
$table->options(['capped' => true, 'size' => 1000000]),仅首次建集合时生效 - 它不碰文档结构,也不做字段迁移;那些事得靠应用层的双态逻辑+后台同步命令(如
php artisan migrate:user-data)
如果你在迁移里写了 $table->string('phone'),那说明你误用了它——MongoDB 不需要声明字段类型。
最容易被忽略的坑:聚合管道里的隐式假设
比字段缺失更危险的是聚合逻辑里的“结构假设”。比如这段代码:
db.orders.aggregate([
{$unwind: "$items"},
{$group: {_id: "$items.product_id", total: {$sum: "$items.quantity"}}}
])
一旦某条订单的 items 是空数组或 null,$unwind 就会跳过整条记录,导致统计结果偏少——这种错误上线后根本不会报错,只会悄悄漏数。
正确做法是加保护:
db.orders.aggregate([
{$set: {items: {$ifNull: ["$items", []]}}},
{$unwind: {path: "$items", preserveNullAndEmptyArrays: true}},
{$group: {_id: "$items.product_id", total: {$sum: "$items.quantity"}}}
])
所有涉及 $unwind、$lookup、$project 的聚合,只要字段可能不存在或类型不一致,就必须显式兜底。这不是过度防御,是文档数据库的生存常识。


















