读写分离需显式配置readpreference,副本集默认只读主节点;secondary可读从但有数据延迟,nearest按延迟选节点;事务和聚合管道等场景有特殊限制。

读写分离不是自动发生的,得靠 readPreference 显式指定
MongoDB 副本集默认所有读请求都发给主节点(readPreference=primary),哪怕从节点空闲也没用。所谓“读写分离”,本质是让读流量绕开主节点,发到从节点——但这必须在驱动层或连接字符串里主动声明策略,服务端不会帮你判断该读哪儿。
常见错误现象:rs.status() 看到从节点状态正常、延迟低,但监控里发现读请求全压在主节点上;或者应用加了从节点却报 NotReadablePrimary 错误(其实是没配对策略)。
-
readPreference=primary:强制读主,写也只走主——这是默认值,不分离 -
readPreference=secondary:只读从,主挂了就报错,适合报表类离线读 -
readPreference=nearest:按网络延迟选最近的可用节点(主或从),适合多机房场景 - Java 驱动里要配
ReadPreference.secondary(),Node.js 用{ readPreference: 'secondary' } - 连接字符串里加
&readPreference=secondary最快验证,但生产环境建议在代码里控制更精细
secondary 读可能返回过期数据,别在强一致性场景硬套
副本集的复制是异步的,secondary 节点的数据永远比主节点慢一点。这个“慢”可能是毫秒级,也可能是秒级——取决于网络、写入压力、oplog 大小。如果你的应用要求“刚写完立刻能读到”,比如用户注册后跳转到个人页,用 readPreference=secondary 就会丢数据。
使用场景边界很关键:
- 适合:日志归档查询、后台统计、历史订单列表(不要求最新状态)
- 不适合:账户余额查询、库存扣减后的状态校验、任何依赖“写后即读”的逻辑
- 可以用
readConcern=majority缓解(需 MongoDB 3.2+ 且副本集开启 majority write concern),但它不能消除延迟,只能保证读到已提交到大多数节点的数据 - 如果业务混合读写,别全局设
secondary,而是在具体查询时临时指定,比如collection.find(...).readPreference('secondary')
从节点不可用时的行为,得提前想清楚降级路径
设了 readPreference=secondary 后,如果所有从节点都掉线或延迟超限(默认 10 秒),驱动默认行为是直接报错,而不是自动切回主节点读。这不是 bug,是设计——避免意外读到陈旧数据变成常态。
容易踩的坑:
- 没配
maxStalenessSeconds或设得过大,导致读到几小时以前的数据(尤其跨时区部署时) - 监控只看节点
stateStr是SECONDARY,但忽略optimeDate和主节点差距,结果线上读到脏数据没人发现 - Node.js 驱动里
secondaryPreferred是更稳妥的选择:优先读从,从挂了自动 fallback 到主,但要注意它不保证“一定读从” - Python PyMongo 的
SecondaryPreferred类似,但得确认你用的是 4.0+ 版本,老版本 fallback 行为不一致
连接字符串和代码里配 readPreference 不等价,优先以代码为准
连接字符串里的 readPreference 是会话级默认值,但驱动允许你在每次操作时覆盖它。很多人配了连接串却还在代码里用 find().readPreference('primary'),结果白配。
参数差异实际影响很大:
- 连接字符串:作用于整个
MongoClient实例,适合全局策略统一的场景 - 命令级设置(如
find({}).readPreference('secondary')):精确控制某次查询,适合混合负载 - 事务内不允许用
secondary,否则报InvalidReadPreference——事务必须读主,这点常被忽略 - 聚合管道里如果用了
$lookup跨库,部分版本(如 4.4 之前)会忽略readPreference,强制走主,得查对应版本文档
复杂点在于:读策略生效位置分散在连接串、客户端选项、会话对象、单次命令四个层级,而且不同驱动实现细节不同。上线前最好用 mongostat 或 Atlas 监控面板,真看一眼读请求到底落在哪个节点上。










