根本原因是驱动默认readPreference为"primary",不显式声明读策略则所有读操作均走主库;常见错误包括代码覆盖连接字符串配置、驱动忽略URI参数、secondary节点状态异常或同步延迟超限。

readPreference=secondary 为什么没生效?
根本原因不是副本集没配好,而是驱动层没声明策略——MongoDB 副本集默认所有读都走 primary,哪怕你加了 5 个从节点,不显式设置 readPreference,流量照样全压在主上。
常见错误现象:
- 监控看到从节点 CPU/网络空闲,但
mongostat显示query全在主节点 - 连接字符串里写了
&readPreference=secondary,但代码里又用collection.find()直接发请求,结果还是走主(Node.js 驱动会忽略 URI 里的全局设置,除非没在代码里覆盖) - 设了
readPreference=secondary却报NotReadablePrimary:其实是驱动发现没有可用 secondary(比如全部延迟超限或状态非 SECONDARY),按设计直接失败,而不是降级回主
验证是否生效最简单的方式:db.runCommand({ replSetGetStatus: 1 }) 看从节点状态,再用 db.setProfilingLevel(2) 开启慢日志,查 system.profile 里 originatingCommand.readPreference 字段值。
Node.js 驱动中怎么设才真正起作用?
优先级顺序是:查询级 > 数据库级 > 客户层级 > 连接字符串。想精细控制,别只靠 URI;想全局统一,也别只设客户端选项。
推荐做法:
- 连接字符串里加
&readPreference=secondary&maxStalenessSeconds=30快速验证,但生产环境慎用 - 客户端初始化时设默认值:
new MongoClient(uri, { readPreference: 'secondary' }) - 关键查询单独指定:
collection.find({ status: 'done' }).readPreference('secondary')(注意:这是 Node.js 4.0+ 写法,旧版用.withReadPreference('secondary')) - 强一致性查询必须绕过 secondary:
collection.findOne({ _id }, { readPreference: 'primary' })
别漏掉 maxStalenessSeconds:它限制从节点数据最多比主节点旧多少秒,默认是 -1(不限),设成 30 意味着延迟超 30 秒的 secondary 不会被选中。
secondary 读可能返回过期数据,哪些场景绝对不能用?
副本集复制是异步的,secondary 的数据永远有延迟。这个延迟不是固定值,受网络抖动、写入压力、oplog 截断影响,可能从毫秒到数秒不等。
以下场景禁用 readPreference=secondary:
- 用户刚提交订单,立刻查订单列表页(可能查不到)
- 库存扣减后立即校验剩余量(可能显示扣前数值)
- 账户余额变更后跳转至资产页(余额数字对不上)
- 任何依赖“写后即读”(read-your-writes)语义的逻辑
如果业务混合强一致 + 弱一致读,不要全局设 secondary,而是在具体集合或操作上动态切换。例如报表服务用 reportCollection.find().readPreference('secondary'),用户服务所有读一律 primary。
阿里云 MongoDB 的只读节点要额外注意什么?
阿里云副本集实例带一个专用 Read-only Node,它不参与选举,只处理读请求,和普通 secondary 行为不同。
关键点:
- 连接字符串里已包含该节点地址,但默认仍走
primary—— 仍需显式设readPreference=nearest或secondary -
nearest在阿里云环境下更稳:它会自动排除只读节点以外的 high-latency 节点,且不强制要求数据新鲜度 - 只读节点不支持写关注(
writeConcern)相关操作,也不能执行rs.stepDown()等管理命令 - 监控里看只读节点的
repl Lag可能恒为 0,但这不代表实时——它只是不参与 oplog 复制链路,数据同步走另一条通路
实际部署时,如果你的应用跨地域访问(比如上海应用连杭州集群),readPreference=nearest 比 secondary 更可靠,它会自动选延迟最低的可用节点,无论是主、从还是只读节点。











