mongodb聚合操作默认走主节点,因readpreference默认为"primary";若需从节点执行,须显式设置readpreference="secondary"、禁用$out/$merge等写操作、并使用readconcern="local"或"available"。

从节点聚合为什么还会触发主节点锁
因为 MongoDB 的 aggregate() 操作在副本集里默认走 readPreference: "primary",哪怕你连的是从节点,驱动或 shell 也会把请求路由到主节点执行——不是从节点“自己算”,而是“代为转发”。这和你手动 rs.secondaryOk() 或显式指定读偏好无关,只要没明确声明,就按默认策略走主节点。
如何确认聚合真正在从节点执行
关键看连接时的读偏好设置和命令是否带 readConcern。常见错误是只改了连接字符串里的 readPreference=secondary,但没同步改应用代码里的实际查询选项。
-
db.runCommand({ aggregate: "orders", pipeline: [ { $match: { status: "shipped" } } ], readPreference: "secondary" })—— 这样才强制走从节点 - Shell 中必须先执行
rs.secondaryOk(),否则即使连的是从节点 IP,db.orders.aggregate(...)仍会重定向 - 使用
readConcern: "available"可绕过 majority 提交等待,但仅限从节点本地快照;若用"majority",仍需主节点参与协调
聚合涉及写操作(如 $out、$merge)时必然锁主节点
这是最容易被忽略的隐性锁源。$out 和 $merge 不是只读,它们会写目标集合,而副本集只允许主节点执行写入。哪怕你在从节点发起命令,mongod 会直接拒绝或自动转发到主节点执行,过程中全程持有主节点的全局写锁(W)。
-
$out:目标集合不存在时会建新集合,等价于drop + insert,必须主节点执行 -
$merge:即使目标集合已存在,只要涉及 upsert 或 delete,也触发主节点写路径 - 即使加了
readPreference: "secondary",这些阶段也会被拦截并报错:Command not supported from secondary
真正想让聚合不碰主节点锁,得满足三个条件
缺一不可。很多团队只改了其中一两个,结果监控里 still see lock contention on primary。
- 连接时明确指定
readPreference=secondary(驱动层或 URI 参数) - 聚合 pipeline 中完全不含
$out、$merge、$facet(某些版本中$facet在从节点上受限)、$lookup跨分片或引用主节点独占集合 - 避免使用需要 majority 确认的
readConcern,比如不要设readConcern: "majority";从节点只支持"local"和"available"











