MongoDB 读关注 / 写关注:数据界的专属 VIP 服务指南

139 阅读8分钟

Java 老铁们!在 MongoDB 这个超大型的数据 “豪华酒店” 里,每天都有无数数据请求来来往往,想要住得舒心、用得安心,就离不开两项超重要的 “VIP 服务”—— 读关注(Read Concern)和写关注(Write Concern)!想象一下,读关注是你查询数据时的 “贴心导购”,能按需给你不同品质的数据;写关注则是写入数据时的 “金牌质检”,确保数据安全落地。要是用不好这俩,数据操作就像开盲盒,结果全靠运气!话不多说,赶紧跟着我解锁 MongoDB 里这两项宝藏服务!

往期精选

一、读关注:数据查询的 “个性化菜单”

1. 读关注是啥?

读关注就是 MongoDB 提供的 “数据查询品质套餐”。你可以根据业务需求,告诉数据库你想要以什么 “品质标准” 读取数据。不同的读关注级别,就像餐厅里不同档次的菜品,有的主打速度,有的强调新鲜度和准确性 ,满足你在各种场景下的查询需求。

2. 读关注的 “菜品清单”

  • local:这是读关注界的 “快餐选项”,主打一个快!它会直接从本地节点读取数据,不考虑数据是否是最新同步的。就像你在便利店随便买个三明治充饥,虽然不一定是现做的,但能快速解决你的 “数据饥饿”。在对数据实时性要求不高,只追求查询速度的场景,比如一些后台统计分析任务,local 就非常合适。
import com.mongodb.MongoClientSettings;
import com.mongodb.client.MongoClients;
import com.mongodb.client.MongoClient;
import com.mongodb.client.MongoCollection;
import com.mongodb.client.MongoDatabase;
import org.bson.Document;
import com.mongodb.ReadPreference;
import com.mongodb.ReadConcern;
public class LocalReadConcernExample {
    public static void main(String[] args) {
        // 配置读关注为local
        MongoClientSettings settings = MongoClientSettings.builder()
               .readPreference(ReadPreference.primary())
               .readConcern(ReadConcern.local())
               .build();
        MongoClient mongoClient = MongoClients.create(settings);
        MongoDatabase database = mongoClient.getDatabase("testdb");
        MongoCollection<Document> collection = database.getCollection("products");
        // 执行查询,对应MongoDB查询语句:db.products.find({}),在查询时应用local读关注
        collection.find().forEach(document -> System.out.println(document));
    }
}
  • majority:堪称读关注里的 “精品套餐”!它会从大多数节点都确认已同步的数据中读取,保证你拿到的是最新、最靠谱的数据。就像你去高档餐厅,每道菜都经过严格筛选和精心烹饪,吃起来绝对放心。在金融交易记录查询、订单状态查询这种对数据准确性要求极高的场景,majority 就是你的不二之选。
import com.mongodb.MongoClientSettings;
import com.mongodb.client.MongoClients;
import com.mongodb.client.MongoClient;
import com.mongodb.client.MongoCollection;
import com.mongodb.client.MongoDatabase;
import org.bson.Document;
import com.mongodb.ReadPreference;
import com.mongodb.ReadConcern;
public class MajorityReadConcernExample {
    public static void main(String[] args) {
        // 配置读关注为majority
        MongoClientSettings settings = MongoClientSettings.builder()
               .readPreference(ReadPreference.primary())
               .readConcern(ReadConcern.majority())
               .build();
        MongoClient mongoClient = MongoClients.create(settings);
        MongoDatabase database = mongoClient.getDatabase("testdb");
        MongoCollection<Document> collection = database.getCollection("products");
        // 执行查询,对应MongoDB查询语句:db.products.find({}),在查询时应用majority读关注
        collection.find().forEach(document -> System.out.println(document));
    }
}
  • linearizable:这是读关注界的 “顶级尊享服务”!它能确保你的读操作看到的是完全线性一致的数据,就像你有一个私人定制的厨师,每道菜都严格按照最高标准制作,一丝一毫都不马虎。不过,这种极致的准确性也会带来一定的性能开销,适合对数据一致性要求达到 “吹毛求疵” 程度的场景,比如银行核心账务系统。
import com.mongodb.MongoClientSettings;
import com.mongodb.client.MongoClients;
import com.mongodb.client.MongoClient;
import com.mongodb.client.MongoCollection;
import com.mongodb.client.MongoDatabase;
import org.bson.Document;
import com.mongodb.ReadPreference;
import com.mongodb.ReadConcern;
public class LinearizableReadConcernExample {
    public static void main(String[] args) {
        // 配置读关注为linearizable
        MongoClientSettings settings = MongoClientSettings.builder()
               .readPreference(ReadPreference.primary())
               .readConcern(ReadConcern.linearizable())
               .build();
        MongoClient mongoClient = MongoClients.create(settings);
        MongoDatabase database = mongoClient.getDatabase("testdb");
        MongoCollection<Document> collection = database.getCollection("products");
        // 执行查询,对应MongoDB查询语句:db.products.find({}),在查询时应用linearizable读关注
        collection.find().forEach(document -> System.out.println(document));
    }
}

二、写关注:数据写入的 “安全卫士”

1. 写关注的使命

写关注就像数据写入时的 “安全质检员”,它能让你指定数据写入到多少个节点后,才认为这次写入操作是成功的。不同的写关注级别,决定了数据写入的 “安全等级”,是追求速度优先,还是把数据安全放在首位,全由你说了算!

2. 写关注的 “安全等级”

  • 0:这是写关注界的 “冒险模式”!选择 0 时,MongoDB 不会给你任何写入确认信息,就像你把一封信扔进邮筒,也不知道它到底寄没寄出去。这种方式速度极快,但风险也高,一旦出现网络故障等问题,数据可能就 “石沉大海” 了。一般用于对数据可靠性要求极低,只追求写入速度的场景,比如一些临时日志记录。
import com.mongodb.client.MongoClients;
import com.mongodb.client.MongoClient;
import com.mongodb.client.MongoCollection;
import com.mongodb.client.MongoDatabase;
import org.bson.Document;
import com.mongodb.WriteConcern;
public class WriteConcernZeroExample {
    public static void main(String[] args) {
        // 配置写关注为0
        MongoClient mongoClient = MongoClients.create("mongodb://localhost:27017");
        MongoDatabase database = mongoClient.getDatabase("testdb");
        MongoCollection<Document> collection = database.getCollection("products");
        collection.withWriteConcern(WriteConcern.UNACKNOWLEDGED)
               .insertOne(new Document("name", "New Product"));
        // 对应MongoDB查询语句:db.products.insertOne({ name: "New Product" }),在插入时应用写关注0
        System.out.println("数据插入完成(无确认)");
    }
}
  • 1:最基础的 “安全保障”!表示数据写入到主节点后,就认为写入成功。就像你把快递寄到快递站,只要快递站签收了,你就觉得任务完成了。这种方式在大多数普通场景下都能满足需求,比如用户注册信息的写入。
import com.mongodb.client.MongoClients;
import com.mongodb.client.MongoClient;
import com.mongodb.client.MongoCollection;
import com.mongodb.client.MongoDatabase;
import org.bson.Document;
import com.mongodb.WriteConcern;
public class WriteConcernOneExample {
    public static void main(String[] args) {
        // 配置写关注为1
        MongoClient mongoClient = MongoClients.create("mongodb://localhost:27017");
        MongoDatabase database = mongoClient.getDatabase("testdb");
        MongoCollection<Document> collection = database.getCollection("products");
        collection.withWriteConcern(WriteConcern.MAJORITY)
               .insertOne(new Document("name", "New Product"));
        // 对应MongoDB查询语句:db.products.insertOne({ name: "New Product" }),在插入时应用写关注1
        System.out.println("数据已写入主节点");
    }
}
  • majority:“高安全性保障”!它要求数据写入到大多数节点(副本集节点数的一半以上)后,才确认写入成功。就像你寄贵重物品,必须等多个快递员都确认送达,才真正放心。在电商订单处理、资金转账这种对数据可靠性要求极高的场景,majority 能给你满满的安全感。
import com.mongodb.client.MongoClients;
import com.mongodb.client.MongoClient;
import com.mongodb.client.MongoCollection;
import com.mongodb.client.MongoDatabase;
import org.bson.Document;
import com.mongodb.WriteConcern;
public class WriteConcernMajorityExample {
    public static void main(String[] args) {
        // 配置写关注为majority
        MongoClient mongoClient = MongoClients.create("mongodb://localhost:27017");
        MongoDatabase database = mongoClient.getDatabase("testdb");
        MongoCollection<Document> collection = database.getCollection("products");
        collection.withWriteConcern(WriteConcern.MAJORITY)
               .insertOne(new Document("name", "New Product"));
        // 对应MongoDB查询语句:db.products.insertOne({ name: "New Product" }),在插入时应用写关注majority
        System.out.println("数据已写入多数节点");
    }
}

三、高阶使用案例:玩转读关注 / 写关注

1. 高并发读场景下的优化

在一个高并发的新闻资讯平台中,大量用户同时访问文章详情页。对于文章内容这种不常更新的数据,我们可以使用 local 读关注来提升查询性能,快速响应大量读请求。而对于文章的评论区,因为评论实时更新频繁,为了让用户看到最新评论,就需要使用 majority 读关注,保证数据的准确性。

import com.mongodb.MongoClientSettings;
import com.mongodb.client.MongoClients;
import com.mongodb.client.MongoClient;
import com.mongodb.client.MongoCollection;
import com.mongodb.client.MongoDatabase;
import org.bson.Document;
import com.mongodb.ReadPreference;
import com.mongodb.ReadConcern;
public class HighConcurrencyReadExample {
    public static void main(String[] args) {
        // 查询文章内容,使用local读关注
        MongoClientSettings articleSettings = MongoClientSettings.builder()
               .readPreference(ReadPreference.primary())
               .readConcern(ReadConcern.local())
               .build();
        MongoClient articleClient = MongoClients.create(articleSettings);
        MongoDatabase articleDb = articleClient.getDatabase("newsdb");
        MongoCollection<Document> articleCollection = articleDb.getCollection("articles");
        articleCollection.find().forEach(document -> System.out.println("文章内容: " + document));
        // 查询评论,使用majority读关注
        MongoClientSettings commentSettings = MongoClientSettings.builder()
               .readPreference(ReadPreference.primary())
               .readConcern(ReadConcern.majority())
               .build();
        MongoClient commentClient = MongoClients.create(commentSettings);
        MongoDatabase commentDb = commentClient.getDatabase("newsdb");
        MongoCollection<Document> commentCollection = commentDb.getCollection("comments");
        commentCollection.find().forEach(document -> System.out.println("评论: " + document));
    }
}

2. 分布式事务中的写关注

在一个分布式电商系统中,当用户下单时,需要同时扣减库存、生成订单并更新用户余额,这些操作必须在一个事务中完成。为了确保数据的一致性和可靠性,我们在事务中使用 majority 写关注。这样,只有当这些操作都成功写入到大多数节点后,事务才会提交,否则就会回滚,避免出现部分操作成功、部分失败导致的数据混乱。

import com.mongodb.client.MongoClients;
import com.mongodb.client.MongoClient;
import com.mongodb.client.MongoCollection;
import com.mongodb.client.MongoDatabase;
import com.mongodb.client.ClientSession;
import org.bson.Document;
import com.mongodb.client.model.Filters;
import com.mongodb.client.model.Updates;
import com.mongodb.WriteConcern;
public class DistributedTransactionWriteConcernExample {
    public static void main(String[] args) {
        MongoClient mongoClient = MongoClients.create("mongodb://localhost:27017");
        MongoDatabase database = mongoClient.getDatabase("ecommercedb");
        MongoCollection<Document> inventoryCollection = database.getCollection("inventory");
        MongoCollection<Document> orderCollection = database.getCollection("orders");
        MongoCollection<Document> userCollection = database.getCollection("users");
        try (ClientSession clientSession = mongoClient.startSession()) {
            clientSession.startTransaction();
            try {
                // 扣减库存,应用majority写关注
                inventoryCollection.withWriteConcern(WriteConcern.MAJORITY)
                       .updateOne(clientSession, Filters.eq("productId", "p001"), Updates.inc("quantity", -1));
                // 生成订单,应用majority写关注
                orderCollection.withWriteConcern(WriteConcern.MAJORITY)
                       .insertOne(clientSession, new Document("orderId", "o001").append("productId", "p001"));
                // 更新用户余额,应用majority写关注
                userCollection.withWriteConcern(WriteConcern.MAJORITY)
                       .updateOne(clientSession, Filters.eq("userId", "u001"), Updates.inc("balance", -100));
                clientSession.commitTransaction();
                System.out.println("分布式事务提交成功");
            } catch (Exception e) {
                clientSession.abortTransaction();
                System.out.println("分布式事务回滚: " + e.getMessage());
            }
        }
    }
}

四、避坑指南:别让 “服务” 变 “雷区”

1. 读关注的 “陷阱”

  • 使用 local 读关注时,一定要清楚业务是否能接受数据的非实时性,不然可能会拿到 “过期” 的数据,就像你吃了昨天的剩饭,容易 “闹肚子”。
  • linearizable 读关注虽然数据一致性超强,但性能开销大,别在普通场景滥用,不然就像开着豪华跑车去买菜,又贵又没必要。

2. 写关注的 “坑”

  • 0 写关注就像一场豪赌,除非你真的对数据丢失无所谓,否则千万别轻易尝试,不然数据丢了都没处哭。
  • 在副本集环境中使用写关注时,要注意节点数量变化对 majority 判断的影响,不然可能会出现写入成功判断错误的情况,就像数错了人数,导致决策失误。

五、总结:成为数据 “服务” 大师

通过这一番探索,相信你已经把 MongoDB 的读关注和写关注摸得透透的了!从基础概念到各种级别,再到高阶使用案例和避坑指南,每一步都像是在解锁数据 “豪华酒店” 的隐藏服务。现在,是时候把这些技能用到实际项目中啦!无论是优化查询性能,还是保障数据安全写入,读关注和写关注都能成为你的得力助手。要是在使用过程中遇到问题,别犹豫,来评论区分享,咱们一起攻克难关!觉得文章有用,点赞、收藏、转发三连走一波,让更多小伙伴也能享受 MongoDB 里这两项超赞的数据 “VIP 服务”!下次咱们继续深挖 MongoDB 的宝藏功能,不见不散!