bigywater.tech
MongoDB · 24

Schema 校验:validationLevel 与 validationAction 的区别

validationLevel 和 validationAction 到底差在哪?加了校验器之后已有的不合法文档会怎样?这篇用同一个 $jsonSchema 逐个组合试一遍,再看错误详情、日志和 insertMany 的行为。

121
不合法文档的错误码,errInfo.details 带每条规则的原因
moderate
不合法的老文档仍可更新,合法文档被改成不合法仍被拒
2 / 1
unordered 的 insertMany([合法, 不合法, 合法]) 插入 2 个、1 个写入错误

校验失败的信息

实测 · 本机执行输出
  test> db.createCollection("people", {validator: {$jsonSchema: {bsonType: "object", required: ["name", "age"], properties: {name: {bsonType: "string", minLength: 2}, age: {bsonType: "int", minimum: 0, maximum: 150}}}}})
  { ok: 1 }
  test> db.people.insertOne({name: "Alice", age: 30})
  {
    acknowledged: true,
    insertedId: ObjectId('6acc3d5114fbb3d4d43eda28')
  }
  test> try { db.people.insertOne({name: "B", age: 200, extra: 1}) } catch (e) { ({code: e.code, message: e.message, details: JSON.stringify(e.errInfo.details)}) }
  {
    code: 121,
    message: 'Document failed validation',
    details: '{"operatorName":"$jsonSchema","schemaRulesNotSatisfied":[{"operatorName":"properties","propertiesNotSatisfied":[{"propertyName":"name","details":[{"operatorNa
  }
  test> try { db.people.insertOne({name: "Carol"}) } catch (e) { e.errInfo.details.schemaRulesNotSatisfied[0].missingProperties }
  [ 'age' ]
  test> try { db.people.insertOne({name: "Dave", age: 30.5}) } catch (e) { JSON.stringify(e.errInfo.details.schemaRulesNotSatisfied[0].propertiesNotSatisfied[0]) }
  {"propertyName":"age","details":[{"operatorName":"bsonType","specifiedAs":{"bsonType":"int"},"reason":"type did not match","consideredValue":30.5,"consideredType":"double

违反校验器的写入报 121 Document failed validation,errInfo.details 里按规则列出原因:缺少哪个字段、类型不匹配(30.5 是 double,不满足 int)、数值超出范围。

validationAction

实测 · 本机执行输出
  test> db.runCommand({collMod: "people", validationAction: "warn"})
  { ok: 1 }
  test> db.people.insertOne({name: "X", age: 999})
  {
    acknowledged: true,
    insertedId: ObjectId('6acc3d5311b2cb41a418ee12')
  }
  mongod 日志里 "Document would fail validation" 的条数: 1
  日志: {"s": "W", "c": "STORAGE", "msg": "Document would fail validation"} ; attr 里的键: ['document', 'errInfo', 'namespace']
  test> db.people.countDocuments({})
  2

warn 下文档照样写入,只在 mongod 日志里记一条 “Document would fail validation”,附带文档与原因。

validationLevel

实测 · 本机执行输出
  test> db.runCommand({collMod: "legacy", validator: {$jsonSchema: {bsonType: "object", required: ["name", "age"], properties: {name: {bsonType: "string", minLength: 2}, age: {bsonType: "int", minimum: 0, maximum: 150}}}}, validationLevel: "strict", validationAction: "error"})
  { ok: 1 }
  test> db.legacy.find({$nor: [<同一个 $jsonSchema>]})    // 加校验器之前就存在的“不合法”文档不会被拒绝,但可以这样找出来
  [ 2 ]
  test> db.runCommand({collMod: "legacy", validationLevel: "strict"})
  { ok: 1 }
  test> db.legacy.updateOne({_id: 2}, {$set: {note: "x"}})    // 老的不合法文档
  Plan executor error during update :: caused by :: Document failed validation
  test> db.legacy.updateOne({_id: 1}, {$set: {age: "x"}})    // 合法文档改成不合法
  Plan executor error during update :: caused by :: Document failed validation
  test> db.runCommand({collMod: "legacy", validationLevel: "moderate"})
  { ok: 1 }
  test> db.legacy.updateOne({_id: 2}, {$set: {note: "x"}})    // 老的不合法文档
  1
  test> db.legacy.updateOne({_id: 1}, {$set: {age: "x"}})    // 合法文档改成不合法
  Plan executor error during update :: caused by :: Document failed validation

加校验器不会检查已有文档。strict 下连不合法的老文档也不能更新;moderate 下老的不合法文档可以更新,但原本合法的文档被改成不合法仍会被拒。

绕过与关闭

实测 · 本机执行输出
  test> db.runCommand({collMod: "legacy", validationLevel: "strict"})
  { ok: 1 }
  test> try { db.legacy.insertOne({_id: 3, name: "Zed", age: -5}) } catch (e) { e.message }
  Document failed validation
  test> db.legacy.insertOne({...不合法...}, {bypassDocumentValidation: true})
  true
  test> db.runCommand({collMod: "legacy", validationLevel: "off"})
  { ok: 1 }
  test> db.legacy.insertOne({_id: 4, ...不合法...})    // validationLevel: off
  true

单次操作可以用 bypassDocumentValidation 绕过;validationLevel: off 则整个集合不再校验。

$expr 校验器

实测 · 本机执行输出
  test> db.createCollection("events", {validator: {$expr: {$lte: ["$start", "$end"]}}})
  { ok: 1 }
  test> db.events.insertOne({start: 1, end: 5}).acknowledged
  true
  test> try { db.events.insertOne({start: 9, end: 5}) } catch (e) { e.message + " / " + JSON.stringify(e.errInfo.details) }
  Document failed validation / {"operatorName":"$expr","specifiedAs":{"$expr":{"$lte":["$start","$end"]}},"reason":"expression did not match","expressionResult":false}

校验器也可以是 $expr,用来校验字段之间的关系,失败详情里是 expressionResult: false。

配置位置与批量写入

实测 · 本机执行输出
  test> db.getCollectionInfos({name: "people"})[0].options
  {
    validator: {
      '$jsonSchema': {
        bsonType: 'object',
        required: [ 'name', 'age' ],
        properties: {
          name: { bsonType: 'string', minLength: 2 },
          age: { bsonType: 'int', minimum: 0, maximum: 150 }
        }
      }
    },
    validationLevel: 'strict',
    validationAction: 'warn'
  }
  test> db.people.insertMany([合法, 不合法, 合法])    // ordered 默认 true
  { inserted: 1, writeErrors: 1, errIndex: 1 }
  test> db.people.countDocuments({name: {$in: ["Ann", "Bob"]}})
  1
  test> db.people.insertMany([合法, 不合法, 合法], {ordered: false})
  { inserted: 2, writeErrors: 1 }

校验器保存在集合 options 里。insertMany 默认 ordered,遇到不合法的文档就停止;ordered: false 则继续插入其余合法的。

分步演示

Schema 校验
本步的对象

    本文用到的工具与边界

    • 本机 macOS 27 arm64 / MongoDB 9.0.2(Homebrew 的 mongod、mongos)/ mongosh 2.12.0 / pymongo 4.18.3;每个实验在临时目录里起全新的 mongod(独立 dbpath,端口 9900 以上,只监听 127.0.0.1,WiredTiger 缓存 0.25 GB),需要副本集或分片集群时在本机起多个进程;命令输出来自真实的 mongosh,批量写入、计时与多线程用 pymongo;计时用 time.perf_counter,数字每次运行会有小幅抖动,ObjectId、时间戳等每次运行都不同。校验器统一用同一个 $jsonSchema;日志里的警告通过解析 mongod 的结构化日志得到
    • 没有验证:$jsonSchema 的全部关键字(pattern、enum、oneOf、additionalProperties 等)的行为;校验器对 upsert 与 findAndModify 的作用;分片集合与副本集上 collMod 修改校验器的传播;校验对写入吞吐的开销;validate 命令对已有不合法文档的报告;校验器里的 $where 或 $text 限制
    京ICP备2026067502号-1京公网安备11010502063487号