readPreference=secondary未走从节点的主因是驱动未真正应用策略:URI参数被客户端配置覆盖、从节点延迟超限未设maxStalenessSeconds、或误传字符串而非ReadPreference常量。

readPreference=secondary 为什么没走从节点?
最常见的情况是:代码里写了 read_preference=ReadPreference.SECONDARY,但监控里所有读请求仍在主节点。根本原因不是副本集没配好,而是驱动没真正应用策略——比如你用的是 PyMongo,却在连接字符串里加了 &readPreference=secondary,但初始化客户端时又没传参,URI 中的参数会被忽略。
另一个高频失效点是:从节点状态不满足条件。驱动会检查每个 secondary 的 optimeDate(即数据延迟),默认不限制;若实际延迟超 1 小时,而你又没设 max_staleness_seconds,它仍可能被选中——但某些版本驱动会因内部健康检查失败直接跳过该节点,导致“可用 secondary 列表为空”,最终 fallback 到 primary 或报错 NotReadablePrimary。
- 必须显式导入并使用常量:
from pymongo import ReadPreference,不能传字符串"secondary" - 连接字符串中的
readPreference参数只在未显式指定客户端级配置时生效 - 验证是否生效:用
db.runCommand({ replSetGetStatus: 1 })看 secondary 状态,再查system.profile中originatingCommand.readPreference字段
PyMongo 中设置 readPreference 的三层优先级
PyMongo 遵循“操作级 > 客户端级 > 连接字符串级”的覆盖规则。靠单一配置容易被覆盖,尤其在封装了通用 DAO 层的项目里。
- 连接字符串(最低优先级):
"mongodb://rs1:27017,rs2:27017/?replicaSet=myRs&readPreference=secondary&maxStalenessSeconds=30" - 客户端初始化(中优先级):
MongoClient(uri, read_preference=ReadPreference.SECONDARY) - 单次查询(最高优先级):
collection.find({}).read_preference(ReadPreference.SECONDARY)
推荐组合:连接字符串设基础值 + 客户端设默认值 + 关键查询单独指定。例如报表类查询强制走 secondary,而订单详情页用 ReadPreference.PRIMARY 显式覆盖。
secondary 模式下读到旧数据是正常行为
从节点异步复制 oplog,readPreference=SECONDARY 不提供任何一致性保证。哪怕你刚在 primary 写入一条记录,立刻用 secondary 读,也可能读不到——这不是 bug,是设计使然。
- 事务内所有读必须用
ReadPreference.PRIMARY,否则抛InvalidOperation - 若需“已提交”语义,得配合
read_concern="majority",但服务端必须启用enableMajorityReadConcern=true -
max_staleness_seconds=30是安全底线:它过滤掉延迟超 30 秒的 secondary,避免读到几小时前的数据(跨时区部署尤其关键)
用 tags 实现更精细的节点路由
单纯 secondary 只能选“任意从节点”,但真实场景常需区分用途:比如把报表查询固定打到硬件较弱、带 { purpose: "report" } 标签的两个节点,避免影响线上流量。
标签路由要两步走:先在副本集配置中为节点打标(rs.conf() 里改 members[n].tags),再执行 rs.reconfig(cfg, { force: true });然后客户端传参:
from pymongo import ReadPreference
read_pref = ReadPreference(
mode=ReadPreference.SECONDARY,
tags=[{"purpose": "report"}]
)
client = MongoClient(uri, read_preference=read_pref)
注意:read_preference_tags 不是独立开关,光设 tags 不设 mode,或 mode 不是 SECONDARY/NEAREST 等支持标签的模式,标签不会生效。
真正容易被忽略的是:标签匹配失败时,驱动默认不降级——它会直接报错,而不是退回到无标签的 secondary。所以生产环境务必搭配 ReadPreference.SECONDARY_PREFERRED 或预设 fallback 标签集。

















