对于 AI 代理:可在 https://www.mongodb.com/zh-cn/docs/llms.txt 获取文档索引—通过在任何 URL 路径后添加 .md 可获取所有页面的 Markdown 版本。
Docs 菜单

update 事件

update

当操作更新集合中的文档时,会发生 update 事件。

注意

消歧

要了解有关修改集合选项时所发生事件的更多信息,请参阅 modify 事件。

字段
类型
说明

_id

文档

一个BSON对象,用作变更流事件的标识符。恢复变更流时,此值用作 resumeAfter 参数的 resumeToken。_id对象中的字段取决于MongoDB版本,在某些情况下,还取决于变更流打开或恢复时的特征兼容性版本(FCV)。

有关通过 resumeToken 恢复变更流的示例,请参阅恢复变更流。

clusterTime

时间戳

clusterTime 是来自与该事件关联的oplog条目的时间戳。

由于oplog大小限制,多文档事务可能会创建多个oplog条目。在ACID 事务中,给定oplog条目中暂存的变更流事件股票相同的 clusterTime。

在分片集群上,具有相同 clusterTime 的事件不一定都与同一事务有关。有些事件与事务完全无关。

要标识单个事务的事件,您可以在变更流事件文档中结合使用 lsid 和 txnNumber。

collectionUUID

UUID

标识发生更改的集合的 UUID。

6.0版本新增。

仅当在创建变更流时将 showExpandedEvents字段设立为 true 时,才会包含 collectionUUID字段。有关详细信息,请参阅 showExpandedEvents。

documentKey

文档

一个文档,其中包含 CRUD 操作创建或修改的文档的 _id 值。

对于分片集合,此字段还显示文档的完整分片键。如果 _id 字段已经是分片键的一部分,则该字段不会重复。

fullDocument

文档

CRUD 操作创建或修改的文档。

只有在您配置变更流时将 fullDocument 设置为 updateLookup,才会出现此字段。在您使用 updateLookup 配置变更流时,该字段表示更新操作所修改文档的当前大多数提交版本。如果任何其他大多数提交操作在原始更新操作和完整文档查找之间修改文档,则该文档可能与 updateDescription 所述的变更不同。

有关更多信息,请参阅查找更新操作的完整文档。

在版本6.0中进行了更改。

从 MongoDB 6.0 开始,如果您使用 db.createCollection()、create 或 collMod 设置 changeStreamPreAndPostImages 选项,则 fullDocument 字段会在插入、替换或更新文档后显示文档(文档后像)。insert 事件总是包括 fullDocument。

fullDocumentBeforeChange

文档

操作应用更改之前的文档。即文档前像。

当您使用 db.createCollection() 方法或 create 或 collMod 命令为集合启用 changeStreamPreAndPostImages 字段时,该字段可用。

6.0版本新增。

lsid

文档

与事务关联的会话标识符。

只有当操作是多文档事务的一部分时才会出现。

ns

文档

受事件影响的命名空间(数据库和/或集合)。

ns.coll

字符串

发生事件的集合的名称。

ns.db

字符串

发生事件的数据库的名称。

operationType

字符串

变更通知报告的操作类型。

为这些变更事件返回 update 值。

updateDescription

文档

一个文档,描述由更新操作更新或删除的字段。

updateDescription.
disambiguatedPaths

文档

一个文档,对 updateDescription 中不明确的字段描述符进行澄清。

当 update 变更事件所述的字段变更是路径包含句点 (.) 或路径包含非数组数字子字段时,disambiguatedPath 字段会为文档提供一个数组,其中列出了修改字段路径中的每个条目。

需要将 showExpandedEvents 选项设置为 true。

6.1版本新增。

updateDescription.
removedFields

阵列

一个字段数组,包含由更新操作删除的字段。

updateDescription.
truncatedArrays

阵列

一个文档数组,用于记录使用一个或多个以下阶段通过基于管道的更新执行的数组截断:

如果替换整个数组,将通过 updateDescription.updatedFields 报告截断。

updateDescription.
truncatedArrays.
field

字符串

截断字段的名称。

updateDescription.
truncatedArrays.
newSize

整型

截断数组中的元素数量。

updateDescription.
updatedFields

文档

一个文档,其键对应于由更新操作修改的字段。每个字段的值对应于这些字段的新值,而不是产生新值的操作。

txnNumber

NumberLong

与 lsid 一起,是一个有助于唯一标识事务的数字。

只有当操作是多文档事务的一部分时才会出现。

wallTime

数据库操作的服务器日期和时间。wallTime 与 clusterTime 的不同之处在于,clusterTime 是从与数据库操作事件相关的 oplog 条目中提取的时间戳。

6.0版本新增。

从 MongoDB 6.0 开始,如果执行以下步骤,您会看到 fullDocumentBeforeChange 文档,其中包含更改(或删除)文档之前的字段:

  1. 使用 db.createCollection()、create 或 collMod 为集合启用新的 changeStreamPreAndPostImages 字段。

  2. 在 db.collection.watch() 中将 fullDocumentBeforeChange 设置为 "required" 或 "whenAvailable"。

变更流输出中的示例 fullDocumentBeforeChange 文档:

"fullDocumentBeforeChange" : {
"_id" : ObjectId("599af247bb69cd89961c986d"),
"userName" : "alice123",
"name" : "Alice Smith"
}

有关变更流输出的完整示例,请参阅使用文档前像和后像的变更流。

如果图像属于以下情况,则前像和后像不可用于变更流事件:

  • 在文档更新或删除操作时未对集合启用。

  • 在 expireAfterSeconds 中设置的前像和后像保留时间后之后被删除。

    • 以下示例将整个集群上的 expireAfterSeconds 设置为 100 秒:

      use admin
      db.runCommand( {
      setClusterParameter:
      { changeStreamOptions: {
      preAndPostImages: { expireAfterSeconds: 100 }
      } }
      } )
    • 以下示例返回当前的 changeStreamOptions 设置,包括 expireAfterSeconds:

      db.adminCommand( { getClusterParameter: "changeStreamOptions" } )
    • 将 expireAfterSeconds 设置为 off 可使用默认保留策略:将保留前像和后像,直到从 oplog 中删除对应的变更流事件。

    • 如果变更流事件从 oplog 中删除,则无论 expireAfterSeconds 前映像和后映像保留时间如何,相应的前映像和后映像也会被删除。

其他考量:

  • 启用前像和后像会占用存储空间并增加处理时间。仅在需要时启用前像和后像。

  • 将变更流事件大小限制为小于 16 MiB。要限制事件大小,您可以:

    • 将文档大小限制为 8 MB。如果其他 change stream 事件字段(例如 updateDescription)不是很大,则可以在 change stream 输出中同时请求更新前的文档和更新后的文档。

    • 如果其他变更流事件字段(例如 updateDescription)并不大,则仅请求变更流输出中最多 16 MiB 的文档的后像。

    • 在以下情况下,仅请求变更流输出中最多 16 MiB 的文档的前像:

      • 文档更新仅影响文档结构或内容的一小部分,且

      • 不会引起 replace 变更事件。replace 事件始终包含后像。

  • 要请求前图像,请在db.collection.watch()中将fullDocumentBeforeChange设置为required或whenAvailable。要请求后图像,您可以使用相同的方法设置fullDocument。

  • 前像被写入 config.system.preimages 集合。

    • config.system.preimages 集合可能会变大。要限制集合大小,可如前文所示为前映像设置 expireAfterSeconds 时间。

    • 要监控 config.system.preimages 的大小,请连接到分片集群上的分片节点或副本集上的 mongod 节点。然后,运行以下命令:

      use config
      db.system.preimages.totalSize()
      db.system.preimages.stats()

      注意

      您必须对 config.system.preimages 集合拥有 collStats 权限操作才能运行这些命令。

      要在 MongoDB Atlas 部署上运行这些命令,您必须具有 atlasAdmin 角色。

    • 前像由后台进程异步删除。

重要

向后不兼容的功能

从 MongoDB 6.0 开始,如果您将文档前图像和后图像用于 change stream,则必须使用 collMod 命令为每个集合禁用 changeStreamPreAndPostImages,然后才能降级到早期 MongoDB 版本。

提示

6.1版本新增。

updateDescription 字段记录操作对文档特定字段所做的变更。这些字段描述符使用点 (.) 作为路径分隔符,使用数字作为数组索引,因此当包含使用点或数字的字段名时,会导致一些歧义。

当 update 事件报告涉及歧义字段的变更时,disambiguatedPaths 文档会为路径键提供数组,列出每个路径组件。

注意

disambiguatedPaths 字段仅适用于使用 showExpandedEvents 选项启动的变更流

例如,一个列出了人员及其居住城镇的文档:

{
"name": "Anthony Trollope",
"home.town": "Oxford",
"residences": [
{"0": "Oxford"},
{"1": "Sunbury"}
]
}
  • 当更新将 home.town 字段从 Oxford 修改为 London 时,将生成如下所示的更新说明:

    "updateDescription": {
    "updatedFields": {
    "home.town": "London"
    },
    "disambiguatedPaths": {
    "home.town": [ "home.town" ]
    }
    }

    由于字段 home.town 包含一个句点,因此 disambiguatedPaths 字段显示一个只有一个值的数组,以指示 town 不是 home 的子字段。

  • 当一项更新修改 residences 数组中的值以进行该变更时,将生成如下所示的更新说明:

    "updateDescription": {
    "updatedFields": {
    "residences.0.0": "London"
    },
    "disambiguatedPaths": { "residences.0.0": [ "residences", 0, "0" ] }
    }

    消除歧义的路径包括整数 0 和字符串 "0",前者表示数组索引,后者表示嵌套文档中的字段名。

{在两种情况下,disambiguatedPath 不包括数字字段:

  • 当路径中的第一个字段是数字字符串时(即 0.name)。这并无歧义,因为第一个字段不能是数组索引。

  • 当数字字符串字段带有前导零时(即 0001)。这并无歧义,因为整数不能有前导零。

update 命令可以根据它对集合所做的实际更改生成不同的更改事件(而不仅仅是 update)。

ChangeEvent
说明

更新操作修改了现有文档。

更新操作替换了文档或生成了比原始文档更详细的差异,从而导致MongoDB替换了该文档。

更新操作尝试更新不存在的文档,而是将该文档添加到集合中。仅当更新在启用 upsert 选项的情况下运行时才会出现这种情况。

在某些情况下,标准更新和管道更新会为同一逻辑更改生成不同的更改事件。如果在标准更新语法和管道更新语法之间切换,则可能会在应用程序中观察到意外的更改事件。

以下示例展示了从嵌入式文档中删除子字段时,不同的更新语法如何产生不同的更改事件。这只是变更事件因所使用的更新语法而异的众多场景之一。

  • 标准更新:删除嵌入式对象的子字段时,删除的路径始终位于 removedFields数组中。

  • 管道更新:删除嵌入式对象的子字段时,生成的变更事件会显示最简洁的更新说明。示例:

    • 如果删除了少量子字段(示例,删除 3 个子字段中的 1),则更改会显示在 removedFields数组中。

    • 如果删除了大多数子字段(示例,删除 3 个子字段中的 2),则更改会显示在 updatedFields数组中,并且父对象会反映新状态。

以下示例脚本显示了标准更新和删除对象字段的管道更新生成的不同变更事件:

// 1. Setup collection and insert documents for test cases
let contactMethods = {
email: "user@example.com",
phone: "555-0100",
mobile: "555-0101"
};
db.cstest.drop();
db.cstest.insertMany([
// Case 1: Remove 1 of 3 subfields
{ _id: "standard_remove_1_of_3", contactMethods: contactMethods },
{ _id: "pipeline_remove_1_of_3", contactMethods: contactMethods },
// Case 2: Remove 2 of 3 subfields
{ _id: "standard_remove_2_of_3", contactMethods: contactMethods },
{ _id: "pipeline_remove_2_of_3", contactMethods: contactMethods }
]);
// 2. Open a Change Stream
const csCursor = db.cstest.watch();
// --- CASE 1: Remove 1 out of 3 subfields ---
db.cstest.updateOne({ _id: "standard_remove_1_of_3" }, { $unset: { "contactMethods.email": "" } });
db.cstest.updateOne({ _id: "pipeline_remove_1_of_3" }, [{ $unset: "contactMethods.email" }]);
// --- CASE 2: Remove 2 out of 3 subfields ---
db.cstest.updateOne({ _id: "standard_remove_2_of_3" }, { $unset: { "contactMethods.email": "", "contactMethods.phone": "" } });
db.cstest.updateOne({ _id: "pipeline_remove_2_of_3" }, [{ $unset: ["contactMethods.email", "contactMethods.phone"] }]);
// 3. Print the change stream updateDescriptions
print("\n--- Change Stream Results ---");
for (let i = 0; i < 4; i++) {
if (csCursor.hasNext()) {
let event = csCursor.next();
print(`\nOperation: ${event.documentKey._id}`);
print(JSON.stringify(event.updateDescription));
}
}
csCursor.close();

对数组的更新会产生 update 更改事件,但 updateDescription.updateFields 可能会显示不同的值。

示例,考虑以下文档和更新:

db.students.insertOne( { student_id: 1, scores: [ ] } )
db.students.updateOne(
{ student_id: 1 },
{ $push: { scores: 0.85 } }
)
db.students.updateOne(
{ student_id: 1 },
{ $push: { scores: 0.94 } }
)
db.students.updateOne(
{ student_id: 1 },
{ $pull: { scores: 0.94 } }
)

第一次更新对空大量进行操作。在这里,$push 会生成 update 更改事件,其中字段被具有给定值的单条目大量替换:

{
_id: { _data: '82642AD66B000000012B022C0100296E5A10045DC4B11BEA5F4319A8E7CAF46816ED71461E5F6964002B060004' },
operationType: 'update',
clusterTime: Timestamp({ t: 1680529003, i: 1 }),
ns: { db: 'communication_chat', coll: 'students' },
documentKey: { student_id: 1 },
updateDescription: {
updatedFields: { scores: [ 0.85 ] },
removedFields: [],
truncatedArrays: []
}
}

在第二次更新操作中,大量现在包含值。$push在大量中添加一个新条目。然后,update 更改事件将其显示为对大量中新位置(即 scores.1)的更改:

{
_id: { _data: '82642AD673000000012B022C0100296E5A10045DC4B11BEA5F4319A8E7CAF46816ED71461E5F6964002B060004' },
operationType: 'update',
clusterTime: Timestamp({ t: 1680529011, i: 1 }),
ns: { db: 'communication_chat', coll: 'students' },
documentKey: { student_id: 1 },
updateDescription: {
updatedFields: { 'scores.1': 0.94 },
removedFields: [],
truncatedArrays: []
}
}

如果您再次运行更新操作以将第三个分数添加到学生的记录中,则会产生修改 scores.2 的 update 更改事件。

使用 $pull 操作符删除大量项会产生一个显示新大量的变更事件:

{
_id: { _data: '82642AD673000000012B022C0100296E5A10045DC4B11BEA5F4319A8E7CAF46816ED71461E5F6964002B060004' },
operationType: 'update',
clusterTime: Timestamp({ t: 1680529011, i: 1 }),
ns: { db: 'communication_chat', coll: 'students' },
documentKey: { student_id: 1 },
updateDescription: {
updatedFields: { scores: [ 0.85 ] },
removedFields: [],
truncatedArrays: []
}
}

通过管道更新减少数组中元素数量的更新操作(例如使用$addFields或$set聚合阶段)会在truncatedArrays字段中显示更新后的大量和新大小。

db.students.insertOne( { student_id: 2, scores: [ 0.85, 0.94, 0.78 ] } )
db.students.updateOne(
{ student_id: 2 },
[ { $addFields: { scores: [ 0.85, 0.94 ] } } ]
)
db.students.updateOne(
{ student_id: 2 },
[ { $addFields: { scores: [ 0.85, 0.94, 0.78 ] } } ]
)

第一次更新的变更事件(使用$addFields阶段从 scores字段中删除一个值)会在 truncatedArrays字段中显示更改:

{
_id: { _data: '82642AD673000000012B022C0100296E5A10045DC4B11BEA5F4319A8E7CAF46816ED71461E5F6964002B060004' },
operationType: 'update',
clusterTime: Timestamp({ t: 1680529011, i: 1 }),
ns: { db: 'communication_chat', coll: 'students' },
documentKey: { student_id: 2 },
updateDescription: {
updatedFields: {},
removedFields: [],
truncatedArrays: [ { fields: "scores", newSize: 2 } ]
}
}

第二次更新的变更事件使用$addFields阶段将值重新添加到 scores字段,并在 updatedFields字段中显示更新:

{
_id: { _data: '82642AD673000000012B022C0100296E5A10045DC4B11BEA5F4319A8E7CAF46816ED71461E5F6964002B060004' },
operationType: 'update',
clusterTime: Timestamp({ t: 1680529011, i: 1 }),
ns: { db: 'communication_chat', coll: 'students' },
documentKey: { student_id: 2 },
updateDescription: {
updatedFields: { scores.2: 0.78 },
removedFields: [],
truncatedArrays: []
}
}

以下示例说明了 update 事件:

{
"_id": { <Resume Token> },
"operationType": "update",
"clusterTime": <Timestamp>,
"wallTime": <ISODate>,
"ns": {
"db": "engineering",
"coll": "users"
},
"documentKey": {
"_id": ObjectId("58a4eb4a30c75625e00d2820")
},
"updateDescription": {
"updatedFields": {
"email": "alice@10gen.com"
},
"removedFields": ["phoneNumber"],
"truncatedArrays": [ {
"field" : "vacation_time",
"newSize" : 36
} ]
}
}

以下示例说明了使用 fullDocument : updateLookup 选项打开的变更流的 update 事件:

{
"_id": { <Resume Token> },
"operationType": "update",
"clusterTime": <Timestamp>,
"wallTime": <ISODate>,
"ns": {
"db": "engineering",
"coll": "users"
},
"documentKey": {
"_id": ObjectId("58a4eb4a30c75625e00d2820")
},
"updateDescription": {
"updatedFields": {
"email": "alice@10gen.com"
},
"removedFields": ["phoneNumber"],
"truncatedArrays": [ {
"field" : "vacation_time",
"newSize" : 36
} ],
"disambiguatedPaths": { }
},
"fullDocument": {
"_id": ObjectId("58a4eb4a30c75625e00d2820"),
"name": "Alice",
"userName": "alice123",
"email": "alice@10gen.com",
"team": "replication"
}
}

fullDocument 文档代表已更新文档的最新多数提交版本。fullDocument 文档可能与更新操作时的文档有所不同,这取决于更新操作和文档查找之间发生的交替多数提交操作的数量。