MongoDB: Schema Validation:数据库层数据校验

Schema Validation 是数据库层的数据校验——即使没有应用层也能拦截脏数据。

数据库层校验的独特价值:为什么有了 mongoose 校验器还需要 Schema Validation?因为 mongoose 校验只在应用层生效——1. 多应用接入:如果多个微服务(Node.js/Python/Go)写入同一个 MongoDB,只有 Node.js 服务有 mongoose 校验,其他服务没有;2. 直接数据库操作:运维用 mongo shell 修复数据、ETL 脚本直接写入,都绕过 mongoose;3. 防御纵深:即使应用层校验有 bug,数据库层仍能拦截。Schema Validation 是最后防线——平时不触发(应用层已拦截),但在异常情况下保护数据完整性。

Schema Validation 的局限性与补偿:MongoDB Schema Validation 有明确局限——1. 不支持跨字段校验(如"endDate > startDate"),需应用层补充;2. 不支持异步校验(如"username 唯一"需查数据库),需唯一索引补充;3. 不支持条件校验(如"type='book' 时 author 必填"),需应用层补充;4. $jsonSchema 不支持所有 MongoDB 操作符(如 $regex 受限)。因此 Schema Validation 不能完全替代应用层校验——正确的做法是应用层做丰富校验(友好的错误提示、跨字段逻辑、异步验证),数据库层做兜底校验(必填/类型/范围/唯一性)。

1. 你将学到


100%
graph LR
    A[客户端插入文档] --> B{mongo<br/>Schema Validation}
    B -->|validationLevel<br/>strict/moderate| C{验证规则}
    C -->|bsonType| D[类型检查]
    C -->|required| E[必填检查]
    C -->|pattern| F[正则检查]
    C -->|enum| G[枚举检查]
    C -->|minLength| H[长度检查]

    D --> I{通过?}
    E --> I
    F --> I
    G --> I
    H --> I

    I -->|是 + action=error| J[✅ 插入成功]
    I -->|否 + action=error| K[❌ 拒绝 + 抛错]
    I -->|否 + action=warn| L[⚠️ 允许 + 警告]

    style J fill:#d4edda
    style K fill:#f8d7da

2. $jsonSchema 验证器

概念说明$jsonSchema 是 MongoDB 3.6+ 引入的文档结构校验语言,基于 JSON Schema 规范。它允许在数据库层定义文档必须满足的结构规则——字段类型、必填字段、值范围、正则模式等。与应用层校验不同,$jsonSchema 由 mongod 引擎强制执行,任何客户端(Python/Java/Node.js)写入都必须遵守。

工作原理:创建带 validator 的集合时,MongoDB 将 $jsonSchema 规则存储在集合元数据中。每次 insert/update 操作,引擎在写入前自动校验文档是否满足规则。校验失败时,根据 validationAction 决定是抛错拒绝还是记录警告。

$jsonSchema 核心关键字

关键字 作用 示例
bsonType 指定 BSON 类型 'string', 'int', 'object', 'array'
required 必填字段列表 ['email', 'username']
properties 字段级规则定义 { email: { bsonType: 'string' } }
pattern 正则校验 '^.+@.+$' (邮箱格式)
enum 枚举值 ['customer', 'admin']
minimum / maximum 数值范围 minimum: 0, maximum: 150
minLength / maxLength 字符串长度 minLength: 3, maxLength: 30
items 数组元素规则 { bsonType: 'string' }
minItems 数组最小长度 minItems: 1

$jsonSchema 与 JSON Schema 的关系:MongoDB 的 $jsonSchema 基于 JSON Schema Draft 4 规范,但有几个关键差异——1. 用 bsonType 替代 type(因为 JSON Schema 不区分 int/double/decimal/objectId 等 BSON 类型);2. additionalProperties 默认 true(允许未定义的字段,与 JSON Schema Draft 4 默认不同);3. 不支持 $ref 引用(所有规则必须内联定义);4. 不支持 format(如 email/uri/date-time,需用 pattern 正则替代)。理解这些差异避免"照抄 JSON Schema 教程代码却报错"的困惑。

$jsonSchema 的嵌套验证:$jsonSchema 支持嵌套对象和数组的递归验证——1. 嵌套对象用 properties + required 定义子结构(如 address: {bsonType: 'object', required: ['city'], properties: {city: {bsonType: 'string'}}});2. 数组用 items 定义元素规则(如 tags: {bsonType: 'array', items: {bsonType: 'string'}} 验证数组元素都是字符串);3. 嵌套深度无硬性限制,但过深的嵌套影响验证性能和可读性——3 层以上考虑拆分为独立集合。嵌套验证是文档模型的核心优势——SQL 需要多表 JOIN 才能验证关联数据,MongoDB 一次验证整个文档树。

使用场景

$jsonSchema 规范与 JSON Schema 的关系:MongoDB 的 $jsonSchema 基于 JSON Schema Draft 4 规范,但做了 BSON 扩展——用 bsonType 替代 type(因为 MongoDB 的类型是 BSON 而非 JSON),增加了 objectId、decimal、date 等 BSON 专用类型。理解这个关系很重要:1. bsonType: 'string' 对应 JSON Schema 的 type: 'string';2. bsonType: 'int' 没有对应(JSON 只有 number);3. required/properties/pattern/enum 与 JSON Schema 完全一致。

版本迁移策略:Schema Validation 的修改需要版本化策略——1. 新增可选字段:低风险,直接 moderate + warn 上线;2. 新增必填字段:中风险,先 optional → 数据迁移 → 再 required;3. 修改字段类型:高风险,双写字段 → 迁移 → 切换 → 删旧字段;4. 收紧值范围:中风险,先 moderate + warn 观察 → 确认无大量违规 → 切换 error。每次变更记录旧规则,必要时可 collMod 回退。

JAVASCRIPT
// === 创建带验证的集合 ===
db.createCollection('users', {
  validator: {
    $jsonSchema: {
      bsonType: 'object',
      required: ['email', 'username'],
      properties: {
        email: {
          bsonType: 'string',
          pattern: '^.+@.+$',
          maxLength: 100
        },
        username: {
          bsonType: 'string',
          minLength: 3,
          maxLength: 30
        },
        age: {
          bsonType: 'int',
          minimum: 0,
          maximum: 150
        },
        role: {
          enum: ['customer', 'admin', 'moderator']
        },
        isActive: {
          bsonType: 'bool'
        }
      }
    }
  },
  validationLevel: 'strict',
  validationAction: 'error'
});

要点解析

  1. bsonType 与 JSON Schema 的 type 不同,MongoDB 使用 BSON 类型名(如 'int' 而非 'number'
  2. required 是顶层关键字,值为字段名数组,不属于任何 property
  3. 嵌套文档通过 properties 嵌套定义,数组元素通过 items 定义

嵌套验证的设计模式:$jsonSchema 的嵌套验证有三种设计模式——1. 全内联模式(地址和订单项直接嵌套在订单的 $jsonSchema 中,结构清晰但文件冗长);2. 变量抽取模式(将 addressSchema 和 itemSchema 定义为 JavaScript 变量,在主 Schema 中引用,代码复用性好但需要在应用层管理);3. 混合模式(核心字段内联、可复用子结构抽取为变量)。生产环境推荐模式 3——地址和订单项可能被多个集合复用(订单和用户都有地址),抽取为独立变量减少重复定义。

数组验证的边界情况:$jsonSchema 的数组验证有几个容易出错的边界情况——1. minItems/maxItems 检查的是数组长度而非文档数量(空数组 [] 通过 minItems: 0 但不通过 minItems: 1);2. items 定义的是所有元素的规则(不支持"前 3 个元素不同类型"的元组验证,JSON Schema Draft 4 支持 but MongoDB 不支持);3. uniqueItems: true 检查数组元素唯一性但对嵌套对象可能不按预期工作(对象按引用比较而非深度比较);4. 空数组 vs null 数组——空数组 [] 通过 bsonType: 'array' 验证,但 null 不通过(需 bsonType: ['array', 'null'] 允许 null)。

▶ 示例 1: 嵌套文档 + 数组校验

JAVASCRIPT
// ShopHub 订单集合:嵌套地址 + 订单项数组校验
db.createCollection('orders', {
  validator: {
    $jsonSchema: {
      bsonType: 'object',
      required: ['userId', 'items', 'total', 'address'],
      properties: {
        userId: { bsonType: 'objectId' },
        items: {
          bsonType: 'array',
          minItems: 1,
          items: {
            bsonType: 'object',
            required: ['productId', 'qty', 'price'],
            properties: {
              productId: { bsonType: 'objectId' },
              qty: { bsonType: 'int', minimum: 1 },
              price: { bsonType: 'decimal', minimum: 0 }
            }
          }
        },
        address: {
          bsonType: 'object',
          required: ['street', 'city', 'zipCode'],
          properties: {
            street: { bsonType: 'string', minLength: 1 },
            city: { bsonType: 'string' },
            zipCode: { bsonType: 'string', pattern: '^[0-9]{5,10}$' }
          }
        },
        total: { bsonType: 'decimal', minimum: 0 }
      }
    }
  },
  validationLevel: 'moderate',
  validationAction: 'error'
});

// 测试:合法订单
db.orders.insertOne({
  userId: ObjectId(),
  items: [{ productId: ObjectId(), qty: Int32(2), price: Decimal128('29.99') }],
  address: { street: '123 Main St', city: 'Seattle', zipCode: '98101' },
  total: Decimal128('59.98')
});
// ✅ 成功

// 测试:空 items 数组
db.orders.insertOne({
  userId: ObjectId(),
  items: [],
  address: { street: '123 Main St', city: 'Seattle', zipCode: '98101' },
  total: Decimal128('0')
});
// ❌ Document failed validation (minItems: 1)

输出:

TEXT 📖 仅展示
// 执行成功

3. validationAction

概念说明validationAction 控制校验失败时 MongoDB 的行为——是严格拒绝(error)还是宽松放行并记录警告(warn)。这是数据完整性与业务连续性之间的关键权衡点。

工作原理

warn 模式的运维价值:warn 模式的核心价值是"零风险上线新规则"——添加新验证规则时先设 warn,观察 1-2 周日志,统计有多少现有写入会被拒绝。如果违规率<1%,说明规则安全,可以切为 error;如果违规率>5%,说明需要调整规则或先清理历史数据。这种渐进式上线策略避免了"一上线就大面积报错"的生产事故。warn 日志查询:db.adminCommand({getLog: 'global'}) 过滤 DocumentFailedValidation 关键词。

warn 日志的监控方案:warn 模式的日志需要主动监控——1. 日志过滤:MongoDB 的 warn 日志混在其他日志中,用 Filebeat/Fluentd 采集后过滤 DocumentFailedValidation 关键词;2. 告警规则:每小时违规数 > 10 触发 Slack/邮件告警(说明规则可能过于严格);3. 违规统计面板:按集合、字段、错误类型分组统计违规次数,判断哪些规则需要调整;4. 自动化报告:每天生成违规摘要报告(X 集合 Y 字段违规 Z 次),发送给 DBA 和后端团队。warn 模式不是"设了就不管",而是"设了就密切观察"——只有持续监控才能安全地将 warn 切为 error。

使用场景

阶段 推荐 action 原因
开发/测试 error 尽早发现数据问题
新规则上线初期 warn 避免阻断业务,观察违规情况
规则稳定后 error 强制执行,确保数据完整性
数据迁移 off 临时关闭,避免旧数据被拒绝
100%
graph LR
    A[写入操作] --> B{Schema Validation}
    B -->|通过| C[✅ 写入成功]
    B -->|失败 + action=error| D[❌ 抛错拒绝]
    B -->|失败 + action=warn| E[⚠️ 写入成功 + 日志警告]

    style C fill:#d4edda
    style D fill:#f8d7da
    style E fill:#fff3cd
action 行为 适用场景
error 拒绝插入/更新(抛错) 生产环境,数据完整性优先
warn 允许但记录警告(不抛错) 渐进上线,观察期
JAVASCRIPT
// === error 模式(推荐生产)===
db.createCollection('users', {
  validator: { $jsonSchema: {...} },
  validationAction: 'error'
});

// === warn 模式(宽松)===
db.createCollection('users', {
  validator: { $jsonSchema: {...} },
  validationAction: 'warn'
});
// 插入不符合的文档:成功 + 警告日志

要点解析

  1. 从 warn 切换到 error 前,建议先分析 warn 日志中的违规频率
  2. warn 模式的日志可通过 db.adminCommand({getLog:'global'}) 查看
  3. 不存在 validationAction: 'off',关闭校验需设 validationLevel: 'off'

4. validationLevel

概念说明validationLevel 决定校验规则应用于哪些文档——仅新文档(moderate)还是包括已存在的旧文档(strict)。这是 Schema 演进的核心配置,决定了新增规则对存量数据的影响。

工作原理

使用场景

场景 推荐 level 原因
全新集合 strict 无历史包袱,全面校验
已有集合新增规则 moderate 避免旧数据无法更新
数据迁移中 off 临时关闭,迁完再开
规则稳定 + 数据干净 strict 最强保护
level 行为 适用场景
strict 验证所有文档(包括已存在的) 新集合、数据干净
moderate 仅验证新插入/更新的文档(推荐) 已有集合、渐进上线
off 不验证 数据迁移
JAVASCRIPT
// === moderate 模式(推荐)===
db.createCollection('users', {
  validator: { $jsonSchema: {...} },
  validationLevel: 'moderate'
});
// 已存在的脏数据不验证,仅验证新数据

要点解析

  1. moderate 是生产环境最常用的 level,它不会阻断对历史脏数据的更新操作
  2. 从 moderate 切换到 strict 前,需要先清理不合规的历史数据
  3. collMod 可以动态修改 validationLevel,无需重建集合

moderate 的隐含风险:moderate 模式的"仅对新数据验证"看似安全,但有隐含风险——1. 旧数据可以无限次更新而不触发验证,脏数据可能越来越脏;2. 应用代码可能依赖 Schema 规则(如假设所有文档都有 email 字段),旧数据不符合时应用崩溃;3. moderate 不是"安全默认值",而是"迁移缓冲期"——应在迁移完成后切为 strict。最佳实践:先用 moderate + warn 观察违规率,违规率降为 0 后切为 strict + error。

数据迁移与验证的配合:Schema 验证与数据迁移需要配合——1. 新增 required 字段时先设 default 值(避免旧文档缺少必填字段),再用迁移脚本回填历史数据;2. 新增 enum 约束时先确定所有历史值都在枚举范围内(否则 moderate 模式下旧数据可更新但 strict 模式下会报错);3. 收紧约束(如 maxlength 从 200 改为 100)时先用 warn 模式观察,确认无超长数据后再切 error。迁移脚本通常用 bulkWrite + $set 批量更新历史数据。


5. 修改已存在集合的验证器

概念说明:生产环境中 Schema 不是一成不变的——业务迭代需要新增字段、修改规则、甚至删除约束。collMod 命令允许在线修改已有集合的验证器,无需重建集合、无需停服。

collMod 的注意事项:collMod 修改验证器是"全量替换"而非"增量合并"——每次调用必须提供完整的 $jsonSchema 规则,即使只改一个字段。这意味着:1. 修改前必须保存当前规则(db.getCollectionInfos() 查看现有验证器);2. 新规则必须包含所有旧字段(否则未列出的字段失去验证);3. 建议用版本控制管理 $jsonSchema 规则(如 Git),方便回滚和审计。删除验证器用空对象 { } + validationLevel: 'off',而非删除 validator 字段。

Schema 版本管理实践:$jsonSchema 定义应纳入版本控制——1. 每个集合一个 JSON 文件(如 validators/users.json、validators/orders.json),与应用代码同仓库管理;2. 部署脚本按依赖顺序执行 collMod(先 users 后 orders,因为 orders 引用 users._id);3. 版本号标注:每个 Schema 文件头部注释版本号和变更日期,方便追踪;4. 回滚方案:Git revert Schema 文件 + 重新执行部署脚本即可回滚;5. CI/CD 集成:部署流水线中自动执行 Schema 迁移脚本,确保代码和 Schema 同步更新。这套实践消除了"代码改了但数据库 Schema 没改"的经典部署问题。

Schema 迁移的灰度发布策略:Schema 变更应像代码一样灰度发布——1. 阶段 1:新增字段为可选(moderate + warn),观察 1-2 周确认无异常;2. 阶段 2:迁移脚本回填历史数据(bulkWrite + $set),确保所有文档都有新字段;3. 阶段 3:新字段设为 required(strict + error),强制新文档必须包含;4. 阶段 4:应用代码开始使用新字段(之前是"有则用之,无则忽略")。每个阶段独立发布,出问题只回滚当前阶段。切忌一次完成所有变更——"先加字段、再回填、再设 required"看似低效,但每步可验证可回滚,是生产安全的唯一方式。

100%
graph LR
    A[分析新需求] --> B[设计新规则]
    B --> C[validationLevel: moderate<br/>validationAction: warn]
    C --> D[观察日志<br/>违规频率]
    D --> E{违规多?}
    E -->|多| F[调整规则/数据迁移]
    E -->|少| G[validationAction: error]
    F --> D
    G --> H[validationLevel: strict<br/>(可选)]

    style C fill:#fff3cd
    style G fill:#d4edda
操作 命令 注意事项
添加验证器 collMod + validator moderate 避免影响旧数据
修改规则 collMod + 新 validator 完整替换,非增量修改
删除验证器 collMod + validator:{} + level:off 临时关闭用
添加必填字段 新字段先 optional,再 required 渐进式,避免阻断写入

Schema 演进的最佳实践:生产环境的 Schema 变更需要谨慎的渐进策略——1. 新增字段:先设 optional + default(旧文档自动填充默认值),运行一段时间确认无问题后再改为 required;2. 收紧约束:先用 validationAction: warn 观察,确认无违规后再切 error;3. 删除字段:先在应用层停止写入该字段,确认旧数据不再被读取后,再用 $unset 批量清理;4. 重命名字段:先添加新字段 + 双写(同时写新旧字段),迁移数据后再删除旧字段。每个步骤都需要回滚方案。

$jsonSchema 与 JSON Schema 的关系:MongoDB 的 $jsonSchema 基于 JSON Schema draft-4 规范,但做了裁剪——支持 bsonType(扩展了 ObjectId、Decimal128 等 BSON 类型)、required、properties、pattern、minimum/maximum、minItems/maxItems 等。不支持的关键字:$ref(不支持外部引用)、definitions(不支持复用定义)、anyOf/oneOf/allOf(不支持组合验证)。这些限制意味着 $jsonSchema 适合"结构性验证"(字段类型+范围+格式),不适合复杂的跨字段逻辑验证——后者应在 mongoose 层实现。

JAVASCRIPT
// === 添加验证器到现有集合 ===
db.runCommand({
  collMod: 'users',
  validator: {
    $jsonSchema: {
      bsonType: 'object',
      required: ['email', 'username'],
      properties: {
        email: { bsonType: 'string', pattern: '^.+@.+$' }
      }
    }
  },
  validationLevel: 'moderate',
  validationAction: 'error'
});

// === 修改验证器 ===
db.runCommand({
  collMod: 'users',
  validator: {
    $jsonSchema: {
      bsonType: 'object',
      required: ['email', 'username', 'age'],  // 新增 age 必填
      properties: {
        email: { bsonType: 'string', pattern: '^.+@.+$' },
        age: { bsonType: 'int', minimum: 0 }
      }
    }
  }
});

// === 移除验证器 ===
db.runCommand({
  collMod: 'users',
  validator: {},
  validationLevel: 'off'
});

要点解析

  1. collMod 的 validator 是完整替换,不是合并——每次修改必须写完整规则
  2. 新增 required 字段时,建议先 moderate + warn,确认旧数据可接受后再 strict + error
  3. 删除验证器用空对象 {},不是删除 validator 字段

6. mongoose Schema vs MongoDB $jsonSchema

概念说明:mongoose Schema 和 MongoDB $jsonSchema 是两层互补的数据校验机制。mongoose 在应用层(Node.js 进程内)校验,灵活但仅对 Node.js 客户端有效;$jsonSchema 在数据库层(mongod 进程内)校验,刚性但对所有客户端生效。

对比分析

维度 mongoose Schema MongoDB $jsonSchema
执行层 应用层(Node.js) 数据库层(mongod)
性能 验证在应用进程 验证在数据库
灵活性 ✅ 异步验证、自定义函数 ❌ 仅静态规则
跨语言 ❌ 仅 Node.js ✅ 任何驱动都生效
复杂校验 ✅ 任意 JS 代码 ❌ 限于 JSON Schema
嵌套校验 ✅ 深度嵌套 + 引用 ✅ 嵌套 properties
自定义错误消息 ✅ 按字段定制 ❌ 通用错误信息
运行时修改 ✅ 动态添加/删除 ✅ collMod 在线修改
100%
graph LR
    A[客户端请求] --> B[mongoose Schema<br/>应用层校验]
    B -->|通过| C[MongoDB $jsonSchema<br/>数据库层校验]
    B -->|失败| D[❌ 应用层拒绝<br/>自定义错误消息]
    C -->|通过| E[✅ 写入成功]
    C -->|失败| F[❌ 数据库拒绝<br/>DocumentFailedValidation]

    style B fill:#cce5ff
    style C fill:#d4edda
    style D fill:#f8d7da
    style F fill:#f8d7da

最佳实践

▶ 示例 2: mongoose + $jsonSchema 双重校验

JAVASCRIPT
// ShopHub:用户注册双重校验
// 1. mongoose 层:灵活校验 + 自定义消息
const userSchema = new mongoose.Schema({
  email: {
    type: String,
    required: [true, 'Email is required'],
    match: [/^[a-zA-Z0-9._%+-]+@[a-zA-Z0-9.-]+\.[a-zA-Z]{2,}$/, 'Invalid email format']
  },
  username: {
    type: String,
    required: true,
    minlength: [3, 'Username must be at least 3 characters'],
    maxlength: 30,
    validate: {
      validator: async function(v) {
        const count = await this.constructor.countDocuments({ username: v });
        return count === 0;
      },
      message: 'Username already exists'
    }
  },
  age: { type: Number, min: 18, max: 120 },
  role: { type: String, enum: ['customer', 'admin', 'moderator'], default: 'customer' }
});

// 2. $jsonSchema 层:基础结构兜底
db.runCommand({
  collMod: 'users',
  validator: {
    $jsonSchema: {
      bsonType: 'object',
      required: ['email', 'username'],
      properties: {
        email: { bsonType: 'string', pattern: '^.+@.+$' },
        username: { bsonType: 'string', minLength: 3 },
        age: { bsonType: 'int', minimum: 18 },
        role: { enum: ['customer', 'admin', 'moderator'] }
      }
    }
  },
  validationLevel: 'moderate',
  validationAction: 'error'
});

// 3. 效果:Node.js 客户端走双重校验,其他客户端走 $jsonSchema 兜底

输出:

TEXT 📖 仅展示
// mongoose 操作成功执行
// 数据库查询/更新结果

7. Schema 演进策略

概念说明:Schema 演进是 MongoDB 无模式数据库面临的核心运维挑战。虽然 MongoDB 不要求预定义 Schema,但生产数据总有隐式结构。当业务需求变化时,需要安全地修改校验规则而不阻断业务。

演进原则

  1. 渐进式:先 optional 再 required,先 warn 再 error
  2. 兼容性:新规则兼容旧数据,不 retroactive 破坏
  3. 可回滚:每次变更记录旧规则,必要时可快速回退
  4. 数据优先:先迁移数据,再收紧规则

Schema 演进的安全操作清单:不同类型的 Schema 变更安全等级不同——1. 安全操作(可直接执行):新增可选字段、增大 maxLength、降低 minimum、新增 enum 值、新增 $jsonSchema 属性(不改变 required);2. 需谨慎(需先迁移数据):新增 required 字段、收紧 minLength/minimum、删除 enum 值、改变 bsonType;3. 高风险(需全面评估):删除字段、改变字段语义(如 age 从"年龄"改为"出生年份")、改变 required 字段类型。安全操作可以直接在生产环境执行,需谨慎操作必须先在 staging 环境验证,高风险操作需要完整的迁移计划 + 回滚方案 + 灰度发布。

Schema 演进的团队协作规范:Schema 变更涉及多个团队——1. 后端团队:定义 Schema 规则 + 编写迁移脚本;2. 前端团队:适配新字段的表单和展示;3. DBA 团队:执行 collMod + 监控数据库性能;4. QA 团队:验证迁移脚本的正确性 + 回滚方案。协作流程:1. 后端提交 Schema 变更 PR(含迁移脚本 + 回滚脚本);2. 前端同步适配 PR;3. Code Review 确认变更影响范围;4. Staging 环境执行迁移 + 验证;5. 生产环境灰度发布(先 warn 后 error);6. 监控 1-2 周确认无异常。规范化流程避免"改了 Schema 但前端不知道"的协作事故。

常见演进模式

演进类型 风险 策略
添加新可选字段 直接添加 property(不 required)
添加新必填字段 先 optional → 数据迁移 → 再 required
修改字段类型 双写字段 → 迁移 → 切换 → 删旧字段
删除字段 先从 required 移除 → 确认无依赖 → 删 property
收紧值范围 先 moderate + warn → 确认 → error

(1) 添加新字段(兼容)

JAVASCRIPT
// ✅ 渐进式演进:默认字段为可选
db.runCommand({
  collMod: 'users',
  validator: {
    $jsonSchema: {
      bsonType: 'object',
      required: ['email', 'username'],  // 老字段仍必填
      properties: {
        email: { bsonType: 'string' },
        username: { bsonType: 'string' },
        age: { bsonType: 'int' }  // 新增字段(不 required)
      }
    }
  },
  validationLevel: 'moderate'
});

(2) 修改字段类型(需数据迁移)

字段类型变更流程

100%
graph LR
    A[1.添加新字段<br/>bsonType:新类型] --> B[2.双写<br/>应用同时写新老字段]
    B --> C[3.数据迁移<br/>老字段→新字段]
    C --> D[4.切换查询<br/>读新字段]
    D --> E[5.删除老字段<br/>确认无依赖]

    style A fill:#cce5ff
    style E fill:#d4edda
JAVASCRIPT
// ⚠️ 修改字段类型需谨慎
// 1. 添加双写字段
db.runCommand({
  collMod: 'users',
  validator: { /* 新增新字段,保留老字段 */ }
});

// 2. 数据迁移脚本
db.users.find({ ageStr: { $exists: true } }).forEach(doc => {
  db.users.updateOne(
    { _id: doc._id },
    { $set: { age: parseInt(doc.ageStr) }, $unset: { ageStr: '' } }
  );
});

// 3. 删除老字段验证

8. 综合实战

概念说明:综合实战将 $jsonSchema 的所有核心特性整合到一个完整的产品集合定义中——包含类型校验、正则、枚举、范围、嵌套文档、数组元素校验等,展示生产级 Schema Validation 的全貌。

生产级 Schema 设计清单

检查项 关键字 是否包含
文档类型 bsonType: 'object'
必填字段 required
字符串长度 minLength / maxLength
正则模式 pattern
数值范围 minimum / maximum
枚举值 enum
数组元素 items
嵌套文档 properties 嵌套
validationLevel moderate
validationAction error
JAVASCRIPT
// === 创建产品集合(含完整 Schema Validation)===
db.createCollection('products', {
  validator: {
    $jsonSchema: {
      bsonType: 'object',
      required: ['sku', 'title', 'price', 'category'],
      properties: {
        sku: {
          bsonType: 'string',
          pattern: '^[A-Z0-9-]+$',
          maxLength: 50
        },
        title: {
          bsonType: 'string',
          minLength: 1,
          maxLength: 200
        },
        price: {
          bsonType: 'decimal'
        },
        category: {
          enum: ['Electronics', 'Books', 'Clothing', 'Home']
        },
        stock: {
          bsonType: 'int',
          minimum: 0
        },
        tags: {
          bsonType: 'array',
          items: { bsonType: 'string' }
        }
      }
    }
  },
  validationLevel: 'moderate',
  validationAction: 'error'
});

▶ 示例:MongoDB $jsonSchema 验证器实战

JAVASCRIPT
// 1. 创建带验证规则的集合
db.createCollection('users', {
  validator: {
    $jsonSchema: {
      bsonType: 'object',
      required: ['email', 'username', 'age'],
      properties: {
        email: {
          bsonType: 'string',
          pattern: '^[a-zA-Z0-9._%+-]+@[a-zA-Z0-9.-]+\.[a-zA-Z]{2,}$',
          maxLength: 100
        },
        username: {
          bsonType: 'string',
          minLength: 3,
          maxLength: 30,
          pattern: '^[a-zA-Z0-9_]+$'
        },
        age: {
          bsonType: 'int',
          minimum: 18,
          maximum: 120
        },
        role: {
          enum: ['customer', 'admin', 'moderator']
        }
      }
    }
  },
  validationLevel: 'moderate',  // 仅验证新插入/更新
  validationAction: 'error'     // 拒绝非法数据
});

// 2. 测试合法数据 → 成功
db.users.insertOne({
  email: 'alice@example.com',
  username: 'alice_2026',
  age: 28,
  role: 'customer'
});
// { acknowledged: true, insertedId: ObjectId('...') }

// 3. 测试非法数据 → 被拒绝
db.users.insertOne({
  email: 'invalid-email',   // 邮箱格式错误
  username: 'ab',            // 用户名太短
  age: 15                    // 未满 18 岁
});
// 抛出错误:Document failed validation
// 错误信息包含字段名和失败原因

// 4. 修改验证器(添加新规则)
db.runCommand({
  collMod: 'users',
  validator: {
    $jsonSchema: {
      bsonType: 'object',
      required: ['email', 'username', 'age', 'phone'],
      properties: {
        email: { bsonType: 'string', pattern: '^.+@.+$' },
        username: { bsonType: 'string', minLength: 3 },
        age: { bsonType: 'int', minimum: 18 },
        phone: { bsonType: 'string', pattern: '^\+?[0-9]{10,15}$' }  // 新增
      }
    }
  }
});

// 5. 嵌套文档验证
db.createCollection('orders', {
  validator: {
    $jsonSchema: {
      bsonType: 'object',
      required: ['userId', 'items', 'total'],
      properties: {
        userId: { bsonType: 'objectId' },
        items: {
          bsonType: 'array',
          minItems: 1,
          items: {
            bsonType: 'object',
            required: ['productId', 'qty', 'price'],
            properties: {
              productId: { bsonType: 'objectId' },
              qty: { bsonType: 'int', minimum: 1 },
              price: { bsonType: 'decimal', minimum: 0 }
            }
          }
        },
        total: { bsonType: 'decimal', minimum: 0 }
      }
    }
  }
});

// 6. 关闭验证器(如数据迁移时)
db.runCommand({
  collMod: 'users',
  validator: {},
  validationLevel: 'off'
});

输出:合法数据成功插入,非法数据被拒绝并报告具体失败字段。validationLevel: moderate 确保已有数据不受影响。

Schema Validation 的运维策略:Schema Validation 在生产环境需要运维配合——1. 上线策略:先设 validationAction: 'warn'(只记录日志不拒绝),观察 1-2 周确认无误拦截,再切换为 error;2. 紧急回滚:准备回滚命令(db.runCommand({collMod: 'users', validator: {}, validationLevel: 'off'})),误拦截时快速关闭验证;3. 数据迁移:迁移脚本前关闭验证(validationLevel: 'off'),迁移后重新开启;4. 监控告警:监控 MongoDB 日志中的 validation failed 事件,频繁失败说明验证规则需要调整;5. 版本管理:将 $jsonSchema 定义存入版本控制(JSON 文件 + 部署脚本),与应用代码同步发布。

验证规则演进的安全路径:Schema 验证规则的修改分三类——1. 放宽规则(安全):新增可选字段、增大 maxLength、降低 minimum,不破坏现有数据;2. 收紧规则(危险):新增 required 字段、缩小 enum 范围、提高 minimum,已有数据可能不满足新规则;3. 类型变更(最危险):bsonType 从 string 改为 int 等,几乎必定导致验证失败。安全演进路径:先放宽(添加新字段为可选)→ 数据补全(脚本为已有文档填充新字段)→ 再收紧(新字段设为 required)。每步间隔 1-2 周,确保数据一致性。

❓ 常见问题

Q $jsonSchema 支持哪些校验?
A bsonType / required / properties / pattern / minLength / maxLength / minimum / maximum / enum 等。
Q mongoose 和 $jsonSchema 哪个更好?
A 双重使用。mongoose 在应用层灵活校验,$jsonSchema 在数据库层兜底。
Q Schema Validation 会影响性能吗?
A 轻微影响。验证在数据库层执行,每个插入/更新都验证。可在高峰期临时关闭。

📖 小节


📝 作业

  1. 基础题(⭐):为 users 集合创建 $jsonSchema 验证(email/username/age)。
  2. 基础题(⭐):测试 validationAction: warn vs error 的行为差异。
  3. 进阶题(⭐⭐):使用 collMod 修改现有集合的验证器(新增字段)。
  4. 进阶题(⭐⭐):mongoose + $jsonSchema 双重校验实现。
  5. 挑战题(⭐⭐⭐):完整产品集合的 $jsonSchema 定义(含嵌套、数组、Decimal128)。
Web-Tutorial.com

Web-Tutorial 技术团队

由多位开发者共同维护的编程教程平台。每篇教程由对应领域的开发者编写和审核,确保内容准确可靠。如发现任何问题,欢迎向我们反馈。

100%

🙏 帮我们做得更好

我们是刚上线的编程教程站,几个人的小团队,精力有限。页面虽经检查,难免还有疏漏——链接失效、排版错乱、内容有误、语言生硬……

如果您发现了,麻烦告诉我们,我们会在收到反馈后第一时间进行修复,再次感谢您的光临 🙏