MongoDB作为典型的文档型数据库,最大的灵活性在于同一个集合里的文档可以拥有完全不同的结构。这种自由在项目初期非常爽快,但随着业务规模扩大,问题会逐渐暴露:有人把字段名写成了user_name,有人写成username;本来该存数字的年龄字段被塞进了字符串;日期格式五花八门。这些脏数据一旦落库,后续的聚合统计和业务逻辑都会遭殃。好在MongoDB提供了模式验证(Schema Validation)机制,允许我们在集合层面定义校验规则,让数据库自己把好数据质量的第一道关。

什么是validator:校验规则的核心载体
MongoDB的模式验证是通过validator选项实现的,它本质上是一个查询表达式或者JSON Schema文档,描述了合法数据应该长什么样。创建集合时可以直接带上验证器,也可以对已存在的集合使用collMod命令追加或修改。验证器支持两种写法:一种是基于查询操作符的过滤表达式,比如$type、$exists、$gte等;另一种是标准的JSON Schema写法,表达能力更强,也是官方推荐的方式。
先看一个创建集合时声明校验规则的例子,使用的是查询表达式风格:
db.createCollection("users", {
validator: {
$jsonSchema: {
bsonType: "object",
required: ["name", "email", "age"],
properties: {
name: {
bsonType: "string",
description: "name必须是字符串且为必填字段"
},
email: {
bsonType: "string",
pattern: "^[a-zA-Z0-9._%+-]+@[a-zA-Z0-9.-]+\\.[a-zA-Z]{2,}$",
description: "email必须符合邮箱格式"
},
age: {
bsonType: "int",
minimum: 0,
maximum: 150,
description: "age必须是0到150之间的整数"
}
}
}
}
})
这段代码中$jsonSchema内部其实就是JSON Schema语法。required数组列出了必填字段,properties对每个字段做了类型和取值约束。一旦验证器生效,任何不满足规则的插入或更新操作都会被数据库拒绝,并返回一个包含失败详情的错误对象,你可以从中拿到errInfo里的具体校验失败原因,定位问题非常方便。
validationLevel与validationAction:控制校验的严格程度
光有验证器还不够,MongoDB还提供了两个参数来精细控制校验行为。validationLevel决定校验规则应用到哪些文档,有三个取值:strict是默认值,对所有插入和更新都执行校验;moderate只对符合现有规则的文档执行校验,对原本就不合规则的存量文档的更新放行;off则完全关闭校验。
moderate这个级别在实际项目中很实用。想象一下,你给一个已经运行两年的老集合加验证规则,里面肯定存在大量不符合新规则的历史数据。如果用strict,这些文档连正常的字段更新都会失败,业务直接受影响。这时候moderate就派上用场了:
db.runCommand({
collMod: "users",
validator: {
$jsonSchema: {
bsonType: "object",
required: ["name", "email"],
properties: {
name: { bsonType: "string" },
email: { bsonType: "string" }
}
}
},
validationLevel: "moderate",
validationAction: "error"
})
另一个参数validationAction只有两个取值:error是默认行为,校验失败直接拒绝写入并抛错;warn则只记录一条日志到mongod日志文件中,数据照样写入。在灰度上线新规则时,warn模式是个很好的过渡手段,先观察日志确认没有大量违规写入,再切换成error强制执行,避免一刀切引发线上故障。
常见校验场景与进阶用法
JSON Schema的能力远不止校验单个字段的类型,它还支持枚举值、嵌套对象、数组元素约束等复杂场景。比如一个订单集合,状态字段只允许几个固定值,商品列表要求每项都包含名称和价格,可以这样写:
db.createCollection("orders", {
validator: {
$jsonSchema: {
bsonType: "object",
required: ["orderNo", "status", "items"],
properties: {
orderNo: { bsonType: "string" },
status: {
enum: ["created", "paid", "shipped", "completed", "cancelled"],
description: "status只能是枚举中的值"
},
items: {
bsonType: "array",
minItems: 1,
items: {
bsonType: "object",
required: ["productName", "price"],
properties: {
productName: { bsonType: "string" },
price: { bsonType: "decimal", minimum: 0 }
}
}
}
}
}
}
})
这里的enum约束了状态枚举,items.items定义了数组每个元素的结构,嵌套校验一层层往下走,表达力和关系型数据库的约束有得一拼。需要注意一点,MongoDB的字段类型用的是BSON类型,比如整数要写int或long而不是number,时间戳是timestamp,十进制数是decimal,写错类型名校验规则会静默失效,这是新手最容易踩的坑之一。
还有一种情况是查询表达式风格的验证器,它直接使用查询操作符,写起来更简洁:
db.createCollection("products", {
validator: {
price: { $type: "decimal", $gt: 0 },
stock: { $type: "int", $gte: 0 },
category: { $in: ["book", "food", "cloth"] }
}
})
这种写法的缺点是无法表达必填字段和嵌套结构,除非配合$and和$exists拼出很长的条件,可读性会明显下降。所以除非规则特别简单,否则建议统一使用$jsonSchema。另外,当某些特殊场景确实需要绕过校验写入数据时,可以在写入时指定bypassDocumentValidation: true选项,比如运维脚本修数、数据迁移等场景,但一定要谨慎使用,避免把校验机制变成摆设。最后提醒一点,修改验证器本身不受已有校验规则的限制,随时可以通过collMod调整规则,配合moderate级别和warn动作,就能实现一套平稳可控的数据质量治理流程。
修改时间:2026-09-07 19:34:35