读写分离需显式设置readpreference,否则默认走主节点;失效主因是驱动未应用策略,而非副本集配置问题。

读写分离不会自动发生,必须在驱动层显式设置 readPreference,否则所有读请求默认走主节点——哪怕你有 5 个空闲的 secondary。
为什么 readPreference=secondary 没生效?
根本原因不是副本集没配好,而是驱动没真正应用该策略。常见失效现象包括:
- 监控看到从节点 CPU/网络空闲,但
mongostat显示query全在主节点 - 连接字符串里写了
&readPreference=secondary,但代码中直接调用collection.find(),Node.js 驱动会忽略 URI 中的全局设置 - 设了
readPreference=secondary却报NotReadablePrimary:驱动发现没有可用 secondary(比如全部延迟超限、状态非SECONDARY或optimeDate差距过大),按设计直接失败,不降级
验证是否生效最直接的方式:db.runCommand({ replSetGetStatus: 1 }) 看从节点状态和 optimeDate 延迟,再用 db.setProfilingLevel(2) 开启慢日志,查 system.profile 中 originatingCommand.readPreference 字段值。
Node.js 驱动中怎么设才真正起作用?
优先级顺序是:查询级 > 数据库级 > 客户层级 > 连接字符串。靠单一配置容易被覆盖。推荐组合使用:
- 连接字符串加基础验证参数:
mongodb://host1:27017,host2:27017/db?replicaSet=rs0&readPreference=secondary&maxStalenessSeconds=30 - 客户端初始化时设默认值:
new MongoClient(uri, { readPreference: 'secondaryPreferred' })(比secondary更稳妥) - 关键查询单独指定:
collection.find({ status: 'done' }).readPreference('secondary')(Node.js 4.0+ 写法;旧版用.withReadPreference('secondary')) - 强一致性读必须绕过从节点:
collection.findOne({ _id }, { readPreference: 'primary' })
maxStalenessSeconds 很关键:它限制从节点数据最多比主节点旧多少秒,默认是 -1(不限),设成 30 意味着延迟超 30 秒的 secondary 不会被选中。跨时区部署时若不设,可能读到几小时以前的数据。
如何用标签(tags)做地理或角色级路由?
readPreferenceTags 是启用标签匹配的开关,光设 readPreference=secondary 不会触发标签路由。标签本身只是元数据,不主动生效。
- 标签必须在副本集配置中设置,且需执行
rs.reconfig(cfg, { force: true })才生效;常见错误是改了rs.conf()但忘了rs.reconfig() - 驱动端传参方式(Node.js):
const readPref = new ReadPreference('secondary', [ { region: 'cn' }, { region: 'fallback' } ]) - 数组内对象是「或」关系(匹配任一即可),每个对象内是「与」关系(必须同时满足所有键值)
- 如果所有标签组合都无匹配节点,驱动会退回到默认
readPreference行为(比如降级到主节点读),不会报错
注意:客户端代码里写的键名(如 region)必须和 rs.conf().members[x].tags 中完全一致,包括大小写,否则匹配失败。
哪些场景绝对不能用 secondary 读?
副本集复制是异步的,secondary 的数据永远有延迟,这个延迟受网络抖动、写入压力、oplog 截断影响,可能从毫秒到数秒不等。以下场景禁用:
- 用户刚提交订单,立刻查订单列表页(可能查不到)
- 库存扣减后立即校验剩余量(可能显示扣前数值)
- 账户余额变更后跳转至资产页(余额数可能滞后)
即使加了 readConcern=majority,也只能保证读到已提交到大多数节点的数据,不能消除延迟本身。混合读写逻辑中,别全局设 secondary,而应在具体查询时临时指定。











