聚合查询不保证跨文档事务一致性,$group、$lookup等阶段读取的是执行时刻的最终一致视图而非事务快照;替代方案包括嵌入关键字段、统一写入路径、前置$match过滤及应用层校验。

聚合查询本身不保证跨文档事务一致性,$group、$lookup 等阶段读取的是查询执行时刻的快照数据,不是事务性视图。想靠聚合“自动”获得强一致性,基本做不到。
聚合查询不参与事务,但能利用因果一致性
聚合操作默认在普通会话中运行,不绑定事务上下文。即使你在事务里执行 db.collection.aggregate(),它也不会被回滚——因为聚合是只读操作,且不纳入事务日志。
不过,如果你用的是因果一致会话(causal_consistency=True),并且配置了 read_concern="majority",那么聚合结果能反映此前该会话中已确认的写入,比如:
- 先
session.with_transaction(...)插入订单 - 再用同一
session执行aggregate() - 只要
read_concern="majority",就能看到刚插入的订单
注意:这不等于“事务内一致性”,只是会话级的读写顺序可见性。
避免因数据不一致导致聚合结果错乱
常见错误是 $lookup 关联时,主集合和被关联集合的数据状态不同步。例如订单表已更新 status,但用户表还没同步,$lookup 拿到旧用户信息。
解决思路不是加事务,而是控制数据源头:
- 把关联字段设计为嵌入式(如把用户昵称存进订单文档),规避
$lookup——单文档原子性天然可靠 - 若必须跨集合关联,确保写入路径统一:所有更新都走同一个服务入口,用
w: "majority"写关注,降低从节点延迟影响 - 对关键字段(如
user_id)加唯一索引,防止脏数据混入
别指望聚合阶段自己“等数据一致”,它只管按当前状态算。
聚合管道里 $match 位置直接影响一致性表现
$match 放得越早,越容易命中索引,也越能过滤掉无效或过期数据。但如果 $match 条件依赖后期计算字段(比如 $addFields 算出的 is_valid),就不得不往后挪——这时一致性风险上升,因为前期已加载大量中间数据。
典型陷阱:
- 在
$lookup后才$match关联字段,导致先拉全量关联数据再过滤,既慢又可能包含临时不一致记录 - 用
$expr在后期做复杂匹配,绕过索引,放大不一致窗口
建议:所有能前置的过滤条件,一律放在第一个 $match;实在要后置,就在应用层补校验,别全信聚合输出。
真正难处理的不是聚合怎么写,而是你有没有掌控数据变更的节奏。聚合只是镜子,照出的是那一刻数据库的真实(或不真实)状态。











