跳至主要内容

MongoDB 架构验证:实用指南与示例

本指南教您如何在 MongoDB 中使用架构验证来强制保持数据干净一致,在灵活性与结构之间取得平衡。
· 12分钟 阅读

使用 AI 探索

MongoDB 也许是无架构的,但这并不意味着您的数据必须杂乱无章。

在本文中,您将从架构验证的基础到使用 $jsonSchema 运算符的高级用例,全面了解相关内容。您将学习如何强制必填字段、限制数据类型、应用模式,以及验证嵌套文档。

我们还将介绍验证在插入与更新期间如何工作、如何修改现有集合,以及何时使用不同的验证级别与动作。

最后,您将能够自信地将架构验证应用到 MongoDB 集合中,在不牺牲 MongoDB 著名灵活性的前提下,保持数据干净且可预测。您也可以查看我们的架构验证视频以进一步了解。

为什么架构验证在 MongoDB 中很重要

MongoDB 的动态文档模型允许同一集合中的文档拥有不同的字段或结构,从而提供了速度与灵活性。这在早期开发或快速迭代期间优势巨大。然而,随着应用的增长,这种灵活性也可能迅速演变为负担,导致数据不一致、不完整或无效。

架构验证 正是为此而生。它允许您为文档定义规则——例如必填字段、特定数据类型或嵌套结构——同时仍然拥抱 MongoDB 的灵活本质。

在本文中,您将学习如何使用 MongoDB 强大的 $jsonSchema 运算符将这些规则应用到您的集合中。无论您是在新的应用中防止错误插入,还是在遗留数据库上补充验证规则,架构验证都能帮助确保数据保持清洁、查询保持可预测、应用保持稳定。

如果您刚开始使用 MongoDB 与 Python,这门MongoDB 入门课程是您在深入架构验证之前,上手查询与集成的绝佳方式。

开始之前

为充分利用本指南,您需要访问一个 MongoDB Atlas 集群。免费层足以跟随学习。

为尽可能清晰、可用地阐释概念,我们将使用一个MongoDB Atlas 示例数据集——具体来说是 sample_mflix 数据库。该数据集包含 movies、comments、users 与 theaters 等集合。我们将在全文中引用这些集合演示每个示例。数据集中的文档包含嵌套对象、数组、字符串、数字以及可选字段等。

加载示例数据集(如需)

1. 前往您的 MongoDB Atlas 控制台。

2. 选择您的集群,点击“…”按钮,并选择 Load Sample Dataset。

3. 等待几分钟,直到 sample_mflix 数据库可用。

从架构验证入门

数据集加载完成后,使用 MongoDB Shell(mongosh)或 MongoDB Compass 连接到您的集群,然后切换到该数据集:

use sample_mflix

使用 findOne() 探索数据集

让我们检查 movies 集合中的一条文档:

db.movies.findOne()

以下是您可能看到的简化版本:

该查询使用 findOne() 从 sample_mflix 数据库的 movies 集合中检索一条示例文档。结果显示多个字段,包括 title、year、genres,以及像 imdb 和 tomatoes.viewer 这样的嵌套对象。

图 1. 该查询使用 findOne() 从 sample_mflix 数据库的 movies 集合中检索一条示例文档。结果显示多个字段,包括 title、year、genres,以及像 imdb 和 tomatoes.viewer 这样的嵌套对象。

此文档包含标量值、数组与嵌套对象的混合,十分适合展示各种验证技术。在下一节中,我们将基于此结构创建一个新集合,并使用 $jsonSchema 定义第一个验证架构。

受支持的关键字、扩展与限制

MongoDB 的 $jsonSchema 验证基于 JSON Schema Draft 4 规范,并做了一些修改与取舍。尽管原规范的所有特性并未全部支持,MongoDB 提供了一个聚焦且实用的子集,足以验证 BSON 文档。

以下是最常用的一些关键字:

  • bsonType:MongoDB 特有的类型关键字(如 "string"、"int"、"array"、"object")
  • required:指定哪些字段必须存在
  • minimum/maximum:用于数值范围验证
  • pattern:使用正则表达式进行字符串格式验证
  • enum:将字段限制为一组允许值
  • items, minItems, maxItems:用于数组验证
  • properties:定义对象内部字段的规则
  • additionalProperties:控制是否允许额外(未定义)的字段

MongoDB 有意省略了完整 JSON Schema 中的一些高级特性,例如:

  • 架构引用($ref)。
  • 条件逻辑(if/then/else)。
  • 组合运算符(oneOf、allOf、anyOf)。

完整的受支持选项列表请查阅 MongoDB 官方 $jsonSchema 文档。

本文将聚焦最实用、最广泛支持的关键字,足以覆盖常见的真实世界验证需求,而不至于过于复杂。

若想更广泛地了解 MongoDB 在 NoSQL 领域中的定位,请阅读这篇综合教程。

创建带有基础验证的集合

既然我们已经了解了 movies 文档的结构,就使用 $jsonSchema 创建一个新集合来强制一些基本规则。我们将从几个常见约束开始:

  • title:必须为字符串,且为必填
  • year:必须为整数,且为必填
  • genres:必须为字符串数组
  • runtime:必须为正整数

我们将该新集合命名为 validated_movies。

创建集合

运行以下命令以带验证地创建集合:

db.createCollection("validated_movies", {
  validator: {
    $jsonSchema: {
      bsonType: "object",
      required: ["title", "year", "runtime"],
      properties: {
        title: {
          bsonType: "string",
          description: "must be a string and is required"
        },
        year: {
          bsonType: "int",
          minimum: 1888,
          description: "must be an integer >= 1888 (year of the first film)"
        },
        runtime: {
          bsonType: "int",
          minimum: 1,
          description: "must be a positive integer and is required"
        },
        genres: {
          bsonType: "array",
          items: {
            bsonType: "string"
          },
          description: "must be an array of strings"
        }
      }
    }
  },
  validationLevel: "strict",
  validationAction: "error"
});

插入有效文档

现在,插入一条符合架构的文档:

db.validated_movies.insertOne({
  title: "Inception",
  year: 2010,
  runtime: 148,
  genres: ["Action", "Sci-Fi", "Thriller"]
});

✅ 这将成功。

插入无效文档

尝试插入缺少 title 字段的文档:

db.validated_movies.insertOne({
  year: 2020,
  runtime: 95,
  genres: ["Drama"]
});

❌ 这将因验证错误而失败,因为 title 为必填。第一个命令将一条有效的电影文档插入 validated_movies 集合,满足所有必需字段和数据类型。第二个命令尝试插入缺少必填字段 title 的文档,因此触发验证错误。

图 2. 第一个命令将一条有效的电影文档插入 validated_movies 集合,满足所有必需字段和数据类型。第二个命令尝试插入缺少必填字段 title 的文档,因此触发验证错误。

处理现有集合

使用架构验证并不需要从零开始。MongoDB 允许您使用 collMod 命令将验证规则应用到现有集合。这在您希望为遗留数据集增加结构、且不影响现有文档时尤其有用。

本节中,我们将为 sample_mflix 数据库中的 movies 集合应用一个验证器。

使用 collMod 添加验证器

让我们强制一条基础规则:每个电影文档都必须具有类型为字符串的 title 字段。

db.runCommand({
  collMod: "movies",
  validator: {
    $jsonSchema: {
      bsonType: "object",
      required: ["title"],
      properties: {
        title: {
          bsonType: "string",
          description: "must be a string and is required"
        }
      }
    }
  },
  validationLevel: "moderate",
  validationAction: "warn"
});

检查集合上的验证规则

使用 collMod 修改 movies 集合后,您可能希望确认当前生效的验证规则。

要检查当前的架构验证器,请运行:

db.getCollectionInfos({ name: "movies" })[0].options.validator

这将返回定义集合验证规则的 $jsonSchema 对象。它是验证您的配置是否正确应用的实用方式,尤其是在从 warn 切换到 error,或更新必填字段结构之后。

理解 validationLevel 与 validationAction

  • validationLevel: "moderate":仅验证正在插入或更新的文档。不匹配架构的现有文档不会触发错误。
  • validationAction: "warn":MongoDB 不会拒绝无效写入,而是记录警告。在生产中,这有助于在强制执行前监控架构违规情况。

尝试插入一条违反架构的文档:

db.movies.insertOne({
  year: 2023
});

您将在日志中看到一条警告,但写入会成功。稍后当您有把握时,可以将动作改为 "error" 来严格执行规则。

由于验证动作设置为

图 3. 由于验证动作设置为 "warn",insertOne() 命令成功添加了一条违反架构规则的文档。MongoDB 接受了写入,但若强制 "error",缺失的 title 字段将触发验证错误。

验证嵌套文档与数组

MongoDB 文档经常包含嵌套结构,如对象中套对象、值的数组。所幸,$jsonSchema 支持对两者进行验证。让我们使用 movies 集合中的字段,演示如何对子文档与数组内容应用规则。

验证嵌套文档

在 movies 集合中,每条文档都包含一个嵌入的 imdb 对象,其中包含 rating、votes 与 id 等字段。我们来强制以下规则:

  • imdb 是一个对象(若存在)。
  • imdb.rating 是 0 到 10 之间的数字。

定义如下:

db.createCollection("validated_nested", {
  validator: {
    $jsonSchema: {
      bsonType: "object",
      properties: {
        imdb: {
          bsonType: "object",
          properties: {
            rating: {
              bsonType: "double",
              minimum: 0,
              maximum: 10,
              description: "Must be a number between 0 and 10"
            }
          }
        }
      }
    }
  }
});

测试嵌套文档

✅ 有效插入:

db.validated_nested.insertOne({
  title: "Interstellar",
  imdb: { rating: 8.6 }
});

❌ 无效插入(rating 超出范围):

db.validated_nested.insertOne({
  title: "Bad Movie",
  imdb: { rating: 12.5 }
});

这将引发验证错误,因为 rating 大于 10。该插入操作失败,因为嵌套 imdb 对象内的 rating 字段超过了架构定义的最大值 10。由于违反了 $jsonSchema 规则,MongoDB 抛出验证错误。      图 4. 该插入操作失败,因为嵌套 imdb 对象内的 rating 字段超过了架构定义的最大值 10。由于违反了 $jsonSchema 规则,MongoDB 抛出验证错误。

验证数组

您也可以对数组施加约束。比如,您希望确保:

  • genres 是字符串数组。
  • 该数组至少包含一个元素。

将以下内容加入您的架构:

genres: {
  bsonType: "array",
  minItems: 1,
  items: {
    bsonType: "string"
  },
  description: "Must be a non-empty array of strings"
}

组合多重约束

在真实应用中,您的文档常常需要同时满足多条验证规则——涵盖必填字段、允许值、类型以及嵌套结构。借助 $jsonSchema,您可以轻松在单个架构定义中组合多重约束。

让我们基于 movies 文档结构构建一个更完整的示例。将强制以下规则:

  • title:必填,且必须为字符串
  • year:必填,整数,≥ 1888
  • rated:可选,但必须匹配预定义值集
  • genres:必须为非空的字符串数组
  • imdb.rating:0 到 10 之间的数字
  • tomatoes.viewer.rating:可选,0 到 5 之间的数字

创建带完整验证的集合

db.createCollection("validated_full", {
  validator: {
    $jsonSchema: {
      bsonType: "object",
      required: ["title", "year"],
      properties: {
        title: {
          bsonType: "string",
          description: "Title is required and must be a string"
        },
        year: {
          bsonType: "int",
          minimum: 1888,
          description: "Year must be an integer >= 1888"
        },
        rated: {
          enum: ["G", "PG", "PG-13", "R", "NC-17"],
          description: "Must be a valid MPAA rating"
        },
        genres: {
          bsonType: "array",
          minItems: 1,
          items: { bsonType: "string" },
          description: "Must be a non-empty array of strings"
        },
        imdb: {
          bsonType: "object",
          properties: {
            rating: {
              bsonType: "double",
              minimum: 0,
              maximum: 10,
              description: "IMDB rating must be between 0 and 10"
            }
          }
        },
        tomatoes: {
          bsonType: "object",
          properties: {
            viewer: {
              bsonType: "object",
              properties: {
                rating: {
                  bsonType: "double",
                  minimum: 0,
                  maximum: 5,
                  description: "Viewer rating must be between 0 and 5"
                }
              }
            }
          }
        }
      }
    }
  }
});

插入有效文档:

db.validated_full.insertOne({
  title: "The Matrix",
  year: 1999,
  rated: "R",
  genres: ["Action", "Sci-Fi"],
  imdb: { rating: 8.7 },
  tomatoes: { viewer: { rating: 4.2 } }
});

插入无效文档:

db.validated_full.insertOne({
  title: "Untitled Project",
  year: 2025,
  rated: "Unrated",
  genres: [],
  imdb: { rating: 15 },
  tomatoes: { viewer: { rating: 6.1 } }
});

将因以下原因失败:

  • rated 值无效。
  • genres 数组为空。
  • imdb.rating 超过 10。
  • tomatoes.viewer.rating 超过 5。

该插入操作因 validated_full 集合中的多项架构违规而失败。rated 字段包含不在枚举中的值,genres 是违反 minItems 规则的空数组,imdb.rating 超过最大值 10 且类型错误,tomatoes.viewer.rating 超过允许的最大值 5。MongoDB 会以详细的验证错误拒绝插入。

图 5. 该插入操作因 validated_full 集合中的多项架构违规而失败。rated 字段包含不在枚举中的值,genres 是违反 minItems 规则的空数组,imdb.rating 超过最大值 10 且类型错误,tomatoes.viewer.rating 超过允许的最大值 5。MongoDB 会以详细的验证错误拒绝插入。

验证级别与动作详解

MongoDB 提供了可精细控制的方式来决定何时以及如何应用架构验证。在定义验证规则时,有两个选项会影响数据库行为:

  • validationLevel:何时应当强制执行规则
  • validationAction:当文档不匹配规则时会发生什么

validationLevel

此选项控制哪些操作会受到架构验证的约束。

  • strict:所有插入与更新都必须符合架构。这是默认且最安全的模式。
  • moderate:仅验证正在插入或更新的文档。即便现有文档无效,也不会被触及。

使用建议:

  • 对新集合使用 strict,从一开始就完全强制执行架构。
  • 当将验证应用到已包含遗留或非结构化数据的集合时,使用 moderate。

validationAction

此选项控制当文档违反验证规则时,MongoDB 的响应方式。

  • error:拒绝写操作,并返回验证错误。
  • warn:接受写入,但服务器内部记录一条警告。

重要提示:在 MongoDB Atlas 上,来自 warn 的警告对用户不可见——您不会在 UI 或标准日志中看到它们。

使用建议:

  • 在开发或逐步上线期间使用 warn,以便在不阻塞写入的情况下监控违规。
  • 当您确信应用写入的文档是有效的,并希望严格执行架构时,切换到 error。

示例:从 Warn 切换到 Error

假设您一开始是这样的:

db.runCommand({
  collMod: "validated_full",
  validationAction: "warn",
  validationLevel: "moderate"
});

在观察应用行为后,您可以切换到严格执行:

db.runCommand({
  collMod: "validated_full",
  validationAction: "error",
  validationLevel: "strict"
});

使用两个 collMod 命令修改 validated_full 集合的验证行为。第一个将 validationAction 设为

图 6. 使用两个 collMod 命令修改 validated_full 集合的验证行为。第一个将 validationAction 设为 "warn"、validationLevel 设为 "moderate",允许插入无效文档同时记录内部警告。第二个命令将 validationAction 设为 "error"、validationLevel 设为 "strict",切换为严格执行,确保所有插入与更新都完全遵循定义的架构。

现在,任何无效的插入或更新都会被错误信息阻止。

常见陷阱与调试技巧

在使用架构验证时,小错误可能导致令人困惑的报错——更糟的是,文档可能悄然绕过您的规则。以下是一些最常见的问题及其规避方法:

错误的 bsonType 值

使用 $jsonSchema 时最常见的错误之一是混淆 JSON 类型与 MongoDB 的“bsonType”。虽然 "string" 与 "array" 直观明了,但数字与布尔值等可能会比较棘手。

例如:

  • 对整数使用 "int" 或 "long"。
  • 对浮点数使用 "double"。
  • 对真/假值使用 "bool"(而非 "boolean")。
  • 对子文档使用 "object"(不是 "dict" 或 "map")。
  • 对列表使用 "array"。

如果不确定字段存储的类型,您可以在 Mongo shell 中使用 typeof 检查文档,或在 MongoDB Compass 中查看数据。

部分更新中缺少必填字段

当使用 $set 时,验证只检查被修改的字段。如果您期望进行完整架构检查,请确保执行整文档替换,或结合使用 validationLevel: "strict" 与应用逻辑来强制完整结构。

本应不通过却通过的空数组

若忘记指定 minItems,空数组会通过验证:

	genres: {
	  bsonType: "array",
	  items: { bsonType: "string" }
	}
	// Accepts empty arrays by default

使用 minItems: 1 以确保至少存在一个值。

warn 模式下没有错误

如前所述,validationAction: "warn" 不会阻止写入,且在 MongoDB Atlas 中不会显示错误。它只会进行内部日志记录,因此验证看起来似乎“没有生效”。

在开发或测试环境中使用 "error" 模式,以便及早捕获问题。

父对象缺失时,不会检查嵌套字段路径

如果要求 imdb.rating 必填,但 imdb 整体缺失,MongoDB 不会检查 rating。

要对嵌套字段施加验证,请确保其父对象要么为必填,要么具有 bsonType: "object"。

查询或更新不匹配架构的文档

将验证应用到现有集合后,识别不符合新架构的文档通常很有帮助。您可以在查询中将 $jsonSchema 与 $nor 或 $not 结合使用。

示例:查找 movies 集合中的无效文档

假设您的架构要求 title 的类型为 "string"。要查找不匹配的文档,您可以运行:

db.movies.find({
  $nor: [
    { $jsonSchema: {
        bsonType: "object",
        required: ["title"],
        properties: {
          title: { bsonType: "string" }
        }
      }
    }
  ]
});

这将返回缺少 title 或 title 类型错误的文档。

该查询在 movies 集合中结合使用 $nor 与 $jsonSchema,查找没有类型为字符串的 title 字段的文档。结果显示了一条仅包含 year 字段、_id 为 683ce0050bd195c35e417534 的文档,确认其违反了要求 title 的架构。此技巧可用于在实施更严格验证规则前识别无效文档。

图 7. 该查询在 movies 集合中结合使用 $nor 与 $jsonSchema,查找没有类型为字符串的 title 字段的文档。结果显示了一条仅包含 year 字段、_id 为 683ce0050bd195c35e417534 的文档,确认其违反了要求 title 的架构。此技巧可用于在实施更严格验证规则前识别无效文档。

您还可以将这种方法用于:

  • 在从 "warn" 切换到 "error" 之前审计遗留文档。
  • 以编程方式清理或修复无效文档。
  • 生成架构合规性报告。

总结:在不牺牲灵活性的前提下获得干净数据

MongoDB 的无架构设计是其最大优势之一,但这并不意味着您应该放任数据不受约束。借助 $jsonSchema,您可以定义强大的验证规则,在利用 MongoDB 灵活性的同时强制结构化。

通过本文,您已经学会:

  • 创建带有验证规则的集合。
  • 将约束应用到新集合与现有集合。
  • 对对象与数组使用嵌套验证。
  • 配置验证模式与动作。
  • 自信地调试并完善您的架构。

无论是构建全新应用,还是为遗留系统增加结构,架构验证都能在不拖慢开发的情况下为数据提供保护。

后续步骤与资源

想要更深入?请查看以下资源:

在准备面试?这些顶级 MongoDB 题目将检验您对架构验证等主题的理解。想要验证您的技能?这份认证指南概述了关于 MongoDB 认证的一切要点。

请记住,从小处着手,先用 "warn" 测试,待有把握后再切换到 "error"——未来的您(以及您的数据)都会感谢现在的选择。

FAQs

使用 $set 进行部分更新时,架构验证如何交互?

当您使用 $set 等操作符进行更新时,除非您使用的是 validationLevel: "strict" 且更新为整文档替换,否则 MongoDB 只会验证被修改的字段,而非整个文档。若要确保完整验证,您可以使用文档替换,或在应用层强制结构。

能否强制仅在另一个字段存在时才要求某字段存在?

不能直接实现。MongoDB 的 $jsonSchema 不支持条件验证(if/then/else)。不过,您有时可以通过基于表达式的验证(例如结合 $expr 与 $cond),或将 $jsonSchema 与 $and 外层包装结合来近似实现。对于更复杂的逻辑,请在应用中处理。

验证规则是在架构默认值或转换之前还是之后评估?

MongoDB 不会在数据库层应用默认值或字段转换。验证严格按照接收到的文档进行。任何默认赋值、类型强制或转换都必须在写入数据库之前由应用逻辑处理。

能否在不破坏旧数据的情况下对架构进行版本化或演进?

可以。常见策略之一是使用 validationLevel: "moderate",同时逐步施加更严格的规则。您也可以为文档添加 schemaVersion 字段,并在应用中应用条件逻辑。MongoDB 不提供内置的架构版本管理,因此版本控制需在外部管理。

架构验证是否会影响插入与更新的性能?

会有些许影响——但在大多数应用中通常影响不大。验证会为每次写入引入少量开销,但它可以通过及早拒绝无效数据,避免后续更高成本的问题。若您在验证深度嵌套或非常大的文档,请进行相应基准测试。

Samuel Molling

作者

Samuel Molling

Samuel Molling 是一名数据库可靠性工程师,热衷于在云端自动化并扩展安全的数据基础设施。

主题