驱动默认 readpreference 为 "primary",即使配置了 secondary 节点,不显式声明读策略,所有读操作仍走主库;优先级为操作级 > 集合级 > 数据库级 > 客户端级,写操作始终走 primary。

为什么设置了Secondary读,请求却全打到Primary
根本原因是驱动默认的 readPreference 是 "primary",哪怕副本集配置了 Secondary 节点,不显式声明读策略,所有读操作仍走主库。
常见错误现象:明明在连接字符串里加了 readPreference=secondary,但监控里 Primary 的读流量没降——大概率是被应用层代码里的显式设置覆盖了;或者用了旧版驱动(如 MongoDB Node.js Driver readPreference 不支持连接字符串传参,只认构造客户端时的选项。
- Node.js 驱动 v4+:连接字符串参数
readPreference=secondary有效,但会被collection.find(..., { readPreference: 'primary' })这类操作级设置覆盖 - Java Driver:必须在
MongoClientSettings中显式设ReadPreference.secondary(),连接串里的readPreference默认不生效 - Python PyMongo:支持连接串参数,但
collection.find(..., read_preference=ReadPreference.PRIMARY)会覆盖全局设置
MaxStalenessSeconds 设置后读不到 Secondary 节点
maxStalenessSeconds 不是“允许延迟多少秒”,而是“要求 Secondary 的 oplog 时间戳不能比 Primary 落后超过该值”——如果节点实际延迟超限,它会被自动从候选列表中剔除,导致读请求 fallback 到 Primary。
典型场景:网络抖动或 Secondary 负载高,造成短暂同步延迟;或者误设了过小的值(比如 maxStalenessSeconds=1),而副本集最小延迟通常都 >2 秒。
- 生产环境建议从
90或120开始试(单位:秒),再根据监控调整 - 该参数仅对
secondary和nearest生效,primaryPreferred下即使 Secondary 可用也不会触发 staleness 检查 - 注意时钟漂移:若 Secondary 系统时间比 Primary 快,可能导致误判为“太新”,同样被排除(虽少见,但真实发生过)
readPreference 在不同层级的优先级怎么算
驱动按“操作级 > 集合级 > 数据库级 > 客户端级”逐层覆盖,不是合并,而是严格覆盖。一个 find() 调用最终用哪个策略,只看它调用时传入的参数,其他地方设的全作废。
容易踩的坑:全局设了 readPreference: 'secondary',但某处业务代码为了强一致性,写了 coll.findOne({ _id }, { readPreference: 'primary' }) ——这个操作就一定走主库,且不会报错、不会警告。
- 连接字符串里的
readPreference属于客户端级,最低优先级 - Mongoose 中
Model.find().read('secondary')是集合级,高于连接串设置 - PyMongo 中
db.collection.find(..., read_preference=ReadPreference.SECONDARY)是操作级,最高优先级 - 写操作永远走 Primary,
readPreference对insertOne、updateOne等完全无效
如何验证读请求真到了 Secondary
不能只看 mongostat 或 Atlas 监控里的“Secondary 查询数”,那只是节点收到的请求量,不等于你发起的读请求——中间可能有代理、分片路由、或驱动 fallback。
最可靠方式是直接在目标 Secondary 节点上抓包或查日志,确认有没有来自你应用 IP 的 find 或 getMore 请求。
- 开启 Secondary 的慢日志:
db.setProfilingLevel(1, { slowms: 0 }),然后查system.profile - 连接到 Secondary 执行
db.currentOp({ "client": /your-app-ip/ }),看是否有活跃读操作 - 用
mongosh连接 Primary 后执行rs.status(),确认 Secondary 状态是"SECONDARY"且"optimeDate"延迟在合理范围 - 避免用
db.getMongo().setReadPref('secondary')测试——这是 shell 会话级设置,不反映驱动行为
真正难的是跨服务、多语言、带连接池的场景:某个 HTTP 请求进来,可能复用了一个之前设过 primary 的连接,也可能新建连接但受负载均衡影响落到不同节点。这时候光看配置没用,得盯住单次请求的完整链路。










