必须显式启用changeStreamPreAndPostImages,否则fullDocumentBeforeChange和fullDocument为空;documentKey是嵌套BSON文档,需用$expr+$eq匹配;监听字段更新应查updateDescription.updatedFields;获取前后文档需三处配置:集群级、集合级启用及watch时指定选项。

必须显式启用 changeStreamPreAndPostImages,否则 fullDocumentBeforeChange 和部分 fullDocument 字段永远为空 —— 这是监听“特定文档更新”最常卡住的地方。
为什么 documentKey 不能直接用来过滤特定文档?
变更流事件里的 documentKey 确实包含被改文档的 _id,但它只是个结构体(如 { "_id": ObjectId("...") }),不能直接在 $match 管道里写 "documentKey._id": ObjectId("...") —— 因为 documentKey 是 BSON 文档,不是扁平字段。MongoDB 会报错或静默不匹配。
- 正确做法是用
$expr+$eq比较嵌套文档:{ $match: { $expr: { $eq: ["$documentKey._id", { $oid: "65a1b2c3d4e5f67890123456" }] } } } - 更稳妥的是把目标 ID 转成字符串,在应用层做二次过滤(尤其当 ID 来自 HTTP 请求等动态输入时)
- 注意:ObjectId 字符串需严格匹配 24 位十六进制,少一位或多一位都会失效
watch() 管道中如何精准匹配某个字段的更新?
想只监听 status 字段被修改的事件?不能靠 fullDocument 做判断(它可能为 null,且性能差),而应利用 updateDescription —— 它是轻量、必存、结构化的增量描述。
- 监听
status字段更新:{ $match: { "updateDescription.updatedFields.status": { $exists: true } } } - 监听
price或discount任一更新:{ $match: { "updateDescription.updatedFields.price": { $exists: true }, "updateDescription.updatedFields.discount": { $exists: true } } }→ 错!这是“且”关系;正确写法是$or数组 - 避免用
$project提取字段再匹配:变更流管道不支持在$match前重写_id,否则直接抛IllegalOperation
如何确保拿到更新前后的完整文档?
默认情况下,update 事件只有 documentKey 和 updateDescription,fullDocument 和 fullDocumentBeforeChange 都为空。这不是代码 bug,而是三处配置缺一不可:
- 数据库级启用前/后像:
db.adminCommand({ setClusterParameter: { changeStreamOptions: { preAndPostImages: { expireAfterSeconds: "off" } } } }) - 集合级开启:
db.runCommand({ collMod: "orders", changeStreamPreAndPostImages: { enabled: true } })(已有集合必须用这个,createCollection仅对新建集合生效) - 监听时指定选项:
collection.watch(pipeline, { fullDocument: "required", fullDocumentBeforeChange: "required" }) - 注意:该配置只对新写入生效,历史文档无前后像;
fullDocumentBeforeChange在delete事件中不可用
resumeToken 断点续传时容易丢掉“刚好那个文档”的更新
重启监听器时若用 startAfter 恢复,会跳过 token 对应事件本身 —— 如果那个事件正是你关心的文档更新,就彻底丢了。必须用 resumeAfter,并确保每次成功处理后立即持久化当前事件的完整 _id(含 _data 字段)。
真正难的不是写对 $match,而是保证:oplog 保留时间够长、副本集状态稳定、resumeToken 存储介质不丢数据、且每次恢复都从上一个已确认处理的位置开始 —— 少一个环节,“特定文档”的更新就可能漏掉一次。











