MongoDB副本集默认把所有读请求都发往主节点,这样能保证写入后的读操作立即看到最新数据,但当读流量较大时,主节点的CPU、内存和连接数都会被读请求大量占用。读取偏好read preference就是用来决定驱动从哪个成员读取数据的策略。它不会改变写入行为,只影响读操作如何选择目标节点。比如你可以让报表查询全部走从节点,让核心交易继续命中主节点,从而在不增加额外组件的情况下实现基础的读写分离。

五种读取偏好分别解决什么问题
MongoDB提供了五种读取偏好模式,最常用的是primary。它是默认值,所有读操作都发送到当前主节点。这种模式的一致性最强,写入后立刻查询一定能读到最新结果,但主节点故障选举期间读操作会短暂失败,同时主节点也要承担全部读压力。
primaryPreferred表示优先读主节点,只有主节点不可用时才允许读从节点。它适合大多数对一致性有一定要求、但又希望主节点故障时读操作不完全中断的业务。需要注意的是,主节点故障后如果从节点数据尚未追平,读到的内容可能是几秒前的旧数据,这是它必须接受的权衡。
secondary则完全相反,所有读请求都发往从节点,主节点不参与读。这种配置适合数据分析、后台报表、批量导出等对实时性不敏感的负载。由于从节点通过异步复制接收数据,查询结果可能明显滞后,而且如果副本集中没有可用从节点,读操作会直接报错。
secondaryPreferred的优先级是先读从节点,从节点全部不可用时回退到主节点。它比secondary多了一层兜底,因此在生产环境中的接受度更高。最后一种是nearest,它不关心节点类型,而是根据网络延迟选择距离客户端最近的成员。这种模式在跨地域部署时很有用,但因为它可能选中主节点也可能选中从节点,所以读到的数据一致性并不固定。
| 模式 | 优先选择 | 回退行为 | 一致性特点 |
|---|---|---|---|
| primary | 主节点 | 不读取从节点 | 强一致 |
| primaryPreferred | 主节点 | 主不可用时读从节点 | 通常一致,故障时可能滞后 |
| secondary | 从节点 | 无从节点时直接失败 | 可能读到旧数据 |
| secondaryPreferred | 从节点 | 无从节点时读主节点 | 通常滞后,回退后一致 |
| nearest | 网络最近成员 | 不区分主从 | 取决于选中的节点 |
从上面的差异可以看出,读取偏好并不是越“分散”越好。很多团队为了降低主节点压力,直接把所有读操作改成secondaryPreferred,结果线上出现“用户刚下单但列表页查不到”的诡异问题。这就是因为写入走了主节点,读取却走了数据还没追平的从节点。
通过连接串和驱动配置读取偏好
最简单的配置方式是在连接字符串中追加参数。连接串里的readPreference参数会作用于该客户端发出的所有读操作,后面的查询不需要再单独指定。下面是一个优先读从节点、并限制从节点最大复制延迟为90秒的URI示例。
mongodb://db0.ipipp.com:27017,db1.ipipp.com:27017,db2.ipipp.com:27017/?replicaSet=rs0&readPreference=secondaryPreferred&maxStalenessSeconds=90
如果使用Python驱动,也可以在创建客户端时通过read_preference参数指定。这样做比修改连接串更直观,也便于在代码里根据环境变量动态切换策略。下面的例子使用SecondaryPreferred,并查询订单库中已支付记录。
from pymongo import MongoClient
from pymongo.read_preferences import SecondaryPreferred
client = MongoClient(
"mongodb://db0.ipipp.com:27017,db1.ipipp.com:27017,db2.ipipp.com:27017/?replicaSet=rs0",
read_preference=SecondaryPreferred()
)
collection = client["orders"]["payments"]
rows = collection.find({"status": "paid"})
for row in rows:
print(row["order_id"])
Java驱动的配置思路类似,只不过多数项目会通过MongoClientSettings构建客户端。下面这段代码先将连接串应用到设置对象,再显式设置读取偏好为secondaryPreferred(),最终创建MongoDB客户端。
import com.mongodb.ConnectionString;
import com.mongodb.MongoClientSettings;
import com.mongodb.ReadPreference;
import com.mongodb.client.MongoClient;
import com.mongodb.client.MongoClients;
ConnectionString connString = new ConnectionString("mongodb://db0:27017,db1:27017,db2:27017/?replicaSet=rs0");
MongoClientSettings settings = MongoClientSettings.builder()
.applyConnectionString(connString)
.readPreference(ReadPreference.secondaryPreferred())
.build();
MongoClient client = MongoClients.create(settings);
在Spring Boot项目里,更常见的做法是直接在配置文件中设置连接串。YAML中的连接串同样支持readPreference参数,应用启动后所有通过MongoTemplate发起的读操作都会遵循这个策略。
spring:
data:
mongodb:
uri: mongodb://db0:27017,db1:27017,db2:27017/?replicaSet=rs0&readPreference=secondaryPreferred&maxStalenessSeconds=120
除了在客户端层面统一配置,也可以在具体集合的查询操作上单独设置读取偏好。例如Python驱动中find方法支持read_preference参数,Java驱动中的withReadPreference也能针对单次操作覆盖默认值。这样做适合“大部分读走主节点,个别报表查询走从节点”的混合场景。
maxStalenessSeconds与标签集如何共同工作
只设置读取偏好还不够,因为从节点可能因为网络抖动、磁盘吞吐不足或复制线程停滞而落后主节点很多。假设业务允许从节点最多落后30秒,那么超过30秒的从节点就不应该被选中。maxStalenessSeconds就是用来过滤这些“掉队”从节点的参数。它配合secondary、secondaryPreferred和nearest使用时效果很明显。
需要注意,maxStalenessSeconds不能与primary一起使用,因为主节点不存在复制延迟的概念。它也不能设置得太小,否则从节点可能因为一次网络抖动就被判定为不可用,导致读操作频繁失败。在secondaryPreferred模式下,如果所有从节点都不满足最大延迟要求,驱动会回退到主节点;而在secondary模式下则会直接抛出错误。
通过mongosh可以快速让当前会话使用指定读取偏好。下面的命令将读取偏好设置为secondaryPreferred,并限制从节点最大延迟为120秒。第二个参数传null表示不配置标签集。
db.getMongo().setReadPref("secondaryPreferred", null, 120)
// 第三参数为120秒,超过这个延迟的从节点不会被选中
标签集是另一个精细控制读节点选择的工具。比如副本集跨多个机房部署时,你可能希望报表读请求优先命中本机房的从节点,而不是跨机房访问。通过给副本集成员打上类似datacenter: "us-east"的标签,再把读取偏好与标签集结合起来,驱动就会优先选择匹配标签的成员。下面这行命令把读取偏好设置为nearest,同时限制只读取us-east机房中负责报表负载的节点。
db.getMongo().setReadPref("nearest", [{"datacenter": "us-east", "workload": "report"}])
// 驱动会按网络延迟从满足标签集的成员中选择节点
标签集在应用程序代码中同样可以配置。比如Java驱动中的ReadPreference.secondaryPreferred(new TagSet(new Tag("datacenter", "us-east"))),不过实际项目中更推荐通过连接串或配置中心统一维护,避免每个开发者在本地写死不同的标签策略。
实战中的选择建议与常见误区
第一个常见误区是认为把读请求全部切到从节点就能解决所有性能问题。从节点读虽然分散了主节点压力,但如果业务代码中存在“写入后立即查询”的流程,secondaryPreferred很可能会读到旧数据。这种情况下可以考虑把写后读操作强制走primary,或者把读关注read concern设置为majority,但这会引入额外的延迟,不能盲目叠加。
第二个误区是以为nearest一定是最快的选择。网络延迟确实是它选择节点的重要依据,但在副本集跨地域部署时,nearest可能选中一个网络更近但复制延迟很大的从节点。如果业务不能接受旧数据,应该优先考虑primary或primaryPreferred,而不是只追求物理距离近。
选举期间的行为也值得单独关注。当主节点发生故障时,副本集会触发选举,在选出新主节点之前,使用primary的读操作会失败,而primaryPreferred会开始读从节点,secondaryPreferred则继续读从节点。因此,对于可用性要求较高的服务,primaryPreferred通常比primary更稳,但你必须接受故障窗口期内可能读到旧数据这一事实。
整体来看,读取偏好的选择取决于业务对一致性和可用性的取舍。核心交易、库存扣减、账户余额等场景应保持primary或至少primaryPreferred;报表统计、历史数据查询、离线分析可以放心使用secondaryPreferred或secondary;跨地域多活或对延迟敏感的边缘查询可以考虑nearest配合标签集使用。配置时再把maxStalenessSeconds作为兜底,才能在不牺牲主节点写入能力的前提下,把读流量合理分散到副本集成员上。
MongoDB读取偏好read preference副本集读操作修改时间:2026-09-22 00:56:26