跳至主要內容

MongoDB 事务

钝悟...大约 12 分钟数据库文档数据库mongodb数据库文档数据库mongodb事务

MongoDB 事务

概述

通俗的说,事务将多个读、写操作捆绑在一起成为一个逻辑操作单元事务中的所有读写是一个执行的整体,整个事务要么成功(提交)、要么失败(中止或回滚)。如果失败,应用程序可以安全地重试。这样,由于不需要担心部分失败的情况(无论出于任何原因),应用层的错误处理就变得简单很多。

大多数 NoSQL 只能部分支持事务,甚至完全不支持事务。但是,MongoDB 支持 ACID 事务,这是它的一大优势。

本文主要介绍了 MongoDB 对于事务的支持力度,以及如何应用事务。

MongoDB 事务简介

在 MongoDB 中,对单个文档的操作具有原子性。由于可以使用嵌入式文档和数组来捕获单个文档结构中数据之间的关系,而无需跨多个文档和集合进行标准化,因此这种单文档原子性消除了许多实际使用案例使用分布式事务的必要性。

对于需要对多文档(在单个或多个集合中)的读写操作具有原子性的情况,MongoDB 支持多文档事务。利用分布式事务,可以跨多个操作、集合、数据库、文档和分片使用事务。

【示例】使用 MongoDB Java Driver 进行事务操作

此示例重点介绍了事务 API 的关键组件。特别是,它使用回调 API。回调 API:

  • 启动事务
  • 执行指定操作
  • 提交结果或出现错误时结束事务
/*
  For a replica set, include the replica set name and a seedlist of the members in the URI string; e.g.
  String uri = "mongodb://mongodb0.example.com:27017,mongodb1.example.com:27017/admin?replicaSet=myRepl";
  For a sharded cluster, connect to the mongos instances.
  For example:
  String uri = "mongodb://mongos0.example.com:27017,mongos1.example.com:27017:27017/admin";
 */
final MongoClient client = MongoClients.create(uri);
/*
    Create collections.
 */
client.getDatabase("mydb1").getCollection("foo")
        .withWriteConcern(WriteConcern.MAJORITY).insertOne(new Document("abc", 0));
client.getDatabase("mydb2").getCollection("bar")
        .withWriteConcern(WriteConcern.MAJORITY).insertOne(new Document("xyz", 0));
/* Step 1: Start a client session. */
final ClientSession clientSession = client.startSession();
/* Step 2: Optional. Define options to use for the transaction. */
TransactionOptions txnOptions = TransactionOptions.builder()
        .writeConcern(WriteConcern.MAJORITY)
        .build();
/* Step 3: Define the sequence of operations to perform inside the transactions. */
TransactionBody txnBody = new TransactionBody<String>() {
    public String execute() {
        MongoCollection<Document> coll1 = client.getDatabase("mydb1").getCollection("foo");
        MongoCollection<Document> coll2 = client.getDatabase("mydb2").getCollection("bar");
        /*
           Important:: You must pass the session to the operations.
         */
        coll1.insertOne(clientSession, new Document("abc", 1));
        coll2.insertOne(clientSession, new Document("xyz", 999));
        return "Inserted into collections in different databases";
    }
};
try {
    /*
       Step 4: Use .withTransaction() to start a transaction,
       execute the callback, and commit (or abort on error).
    */
    clientSession.withTransaction(txnBody, txnOptions);
} catch (RuntimeException e) {
    // some error handling
} finally {
    clientSession.close();
}

writeConcern 可以决定写操作到达多少个节点才算成功。

  • 默认:多节点复制集不做任何设定,所以是有可能丢失数据。
  • w: "majority":大部分节点确认,就视为写成功
  • w: "all":全部节点确认,才视为写成功

journal 则定义如何才算成功。取值包括:

  • true:写操作落到 journal 文件中才算成功;
  • false:写操作达到内存即算作成功。

MongoDB 分布式事务

对于需要对多个文档(在单个或多个集合中)原子性读取和写入的情况,MongoDB 支持分布式事务,包括副本集和分片集群上的事务。可以跨多个操作、集合、数据库、文档和分片使用分布式事务。

分布式事务具有原子性:

  • 事务要么应用所有数据更改,要么回滚更改。
  • 在事务提交时,事务中所做的所有数据更改都会保存,并且在事务之外可见。
    • 在事务进行提交前,在事务中所做的数据更改在事务外不可见。
    • 不过,当事务写入多个分片时,并非所有外部读取操作都需等待已提交事务的结果在各个分片上可见。例如,如果事务已提交并且写入 1 在分片 A 上可见,但写入 2 在分片 B 上尚不可见,则读关注 "local"open in new window 处的外部读取可以在不看到写入 2 的情况下读取写入 1 的结果。
  • 事务中止后,在事务中所做的所有数据更改会被丢弃且不会变得可见。例如,如果事务中的任何操作失败,事务就会中止,事务中所做的所有数据更改将被丢弃且不会变得可见。

要点:在大多数情况下,与单文档写入操作相比,分布式事务会产生更高的性能成本,并且分布式事务的可用性不应取代有效的模式设计。在许多情况下,非规范化数据模型(嵌入式文档和数组)open in new window 仍然是数据和使用案例的最佳选择。换言之,对于许多场景,适当的数据建模将最大限度地减少对分布式事务的需求。

MongoDB 事务操作

可以跨多个操作、集合、数据库、文档和分片使用分布式事务。

对于事务:

  • 可以在事务中创建集合和索引。
  • 事务中使用的集合可以位于不同的数据库中。

在事务中创建集合和索引

如果事务不是跨分片写入事务,则可以在 分布式事务open in new window 中执行以下操作:

  • 创建集合。
  • 在先前同一事务中创建的新空集合上创建索引。

在事务中创建集合时:

在事务内创建索引open in new window 时,要创建的索引必须位于以下位置之一:

  • 不存在的集合。集合作为操作的一部分创建。
  • 先前在同一事务中创建的新空集合。

计数操作

要在事务内执行计数操作,请使用 $countopen in new window 聚合阶段或 $groupopen in new window(带有 $sumopen in new window 表达式)聚合阶段。

MongoDB 驱动程序提供集合级 API countDocuments(filter, options) 作为辅助方法,该方法使用 $groupopen in new window$sumopen in new window 表达式来执行计数。

mongoshopen in new window 提供 db.collection.countDocuments()open in new window 辅助方法,该方法使用 $groupopen in new window$sumopen in new window 表达式进行计数。

去重操作

如要在事务中执行不同的操作:

信息操作

事务中允许使用诸如 helloopen in new windowbuildInfoopen in new windowconnectionStatusopen in new window(及其辅助方法)之类的信息命令,但它们不能是事务中的第一项操作。

事务操作限制

事务中不允许执行以下操作:

事务和会话

  • 事务与会话关联。
  • 一个会话一次最多可以具有一个未结事务。
  • 使用驱动程序时,事务中的每项操作都必须与会话关联。有关详细信息,请参阅驱动程序特定文档。
  • 如果会话结束并且具有打开的事务,则事务将中止。

读关注/写关注/读取偏好

事务和读取偏好

事务中的操作使用事务级 读取偏好open in new window

使用驱动程序,可以在事务启动时设置事务级 读取偏好open in new window

  • 如果未设置事务级别的读取偏好,则事务将使用会话级别的读取偏好。
  • 如果未设置事务级别和会话级别的读取偏好,则事务将使用客户端级别的读取偏好。默认情况下,客户端级别的读取偏好为 primaryopen in new window

包含读取操作的 分布式事务open in new window 必须使用读取偏好 primaryopen in new window。给定事务中的所有操作必须路由到同一节点。

事务和读关注

事务中的操作使用事务级 读关注open in new window。也就是说,在集合和数据库级别设置的任何读关注在事务中都会被忽略。

可以在事务启动时设置事务级别的 读关注open in new window

事务支持以下读关注级别:

"local"

"majority"

"snapshot"

事务和写关注

事务使用事务级 写关注open in new window 来提交写入操作。事务内的写入操作必须在没有明确写关注规范的情况下执行,并须使用默认的写关注。在提交时,使用事务级写关注来提交写入。

可以在事务启动时设置事务级写关注open in new window

参考资料

评论
  • 按正序
  • 按倒序
  • 按热度
Powered by Waline v2.15.7