Change Streams 本身不触发,仅被动监听写操作;监听不到 update 的常见原因包括:单机模式不支持、未设置 readConcern: "majority"、集合名错误、update 未实际修改文档,以及 resume token 过期。

Change Streams 本身不“触发”,它只是被动监听已发生的写操作;你更新数据时,只要满足前提条件,Change Stream 就会自然收到事件——关键不在“怎么触发”,而在“怎么确保能被监听到”。
变更流监听不到 update 的常见原因
多数人以为改了数据就该出事件,结果 watch() 一直没反应,通常卡在这几个点:
- MongoDB 运行在单机模式:Change Streams 要求副本集(
Replica Set)或分片集群,单节点mongod直接不支持,连watch()都会抛CommandFailedException - 未设置
readConcern: "majority":Java 驱动默认不带这个,必须显式配置,否则连接成功但收不到事件(尤其在写入后立即 watch) - 集合名/数据库名拼错,或用了不存在的集合:
db.collection.watch()不报错,但游标空转——因为没变更可播 - update 操作没真正修改文档:比如
{$set: {status: "active"}}但原值已是"active",MongoDB 内部跳过写入,oplog 无记录,Change Stream 当然沉默
Java 中正确开启监听并捕获 update 事件
以 Reactive Streams 驱动为例,重点不是调用 watch(),而是配对使用 withReadConcern() 和正确的聚合管道:
ReadConcern majority = ReadConcern.builder().level("majority").build();
MongoCollection<Document> restaurants = mongoDatabase
.getCollection("restaurants")
.withReadConcern(majority);
ChangeStreamPublisher<Document> publisher = restaurants.watch(
Arrays.asList(
Aggregates.match(Filters.eq("operationType", "update")),
Aggregates.project(Projections.fields(
Projections.include("documentKey", "updateDescription"),
Projections.excludeId()
))
)
);
Flux.from(publisher)
.doOnNext(change -> {
System.out.println("Update on _id: " + change.get("documentKey"));
System.out.println("Fields changed: " + change.get("updateDescription"));
})
.blockLast();
注意三点:
-
withReadConcern()必须作用于MongoCollection实例,不能只设在MongoClient级别 - 聚合管道里用
$match过滤operationType是安全的,但禁止修改_id字段(MongoDB 4.2+ 会直接中断流) -
fullDocument默认为null,如需拿到更新后的完整文档,得在watch()时加选项:new ChangeStreamOptions().fullDocument(FullDocument.UPDATE_LOOKUP)
mongosh 里快速验证 update 是否被监听
不用写代码,直接在 mongosh 里开两个窗口:
- 窗口 A 执行:
db.restaurants.watch([{$match: {operationType: "update"}}])—— 这会挂起等待输出 - 窗口 B 执行:
db.restaurants.updateOne({name: "Blarney Castle"}, {$set: {cuisine: "Traditional Irish"}}) - 如果窗口 A 立即打出一个带
operationType: "update"的文档,说明链路通了;如果没反应,先检查rs.status()确认副本集状态,再查db.runCommand({getParameter:1, readConcernMajorityJournalDefault:true})确认 majority 配置生效
最容易被忽略的是 resume token 的生命周期管理——它不是永久有效的,MongoDB 默认只保留最近 24 小时的 oplog,断连超时或重启后若用过期 token 调 startAfter,会直接报 ResumeTokenNotFound。生产环境必须持久化最新 token,并接受“最多一次”语义,而非“恰好一次”。


















