MongoDB事务详解

一、事务

在MongoDB中,对单个文档的操作是原子操作。因为您可以使用嵌入式文档和数组来捕获单个文档结构中数据之间的关系,而不是跨多个文档和集合进行规范化,所以这种单个文档原子性消除了许多实际用例对多文档事务的需要。
对于需要对多个文档(在单个或多个集合中)进行原子性读写的情况,MongoDB支持多文档事务。对于分布式事务,事务可以跨多个操作、集合、数据库、文档和碎片使用。

二、事务 API

此示例突出显示了事务API的关键组件。特别是,它使用回调API。回调API:

  • 启动事务
  • 执行指定的操作
  • 提交结果(或出现错误时中止)

回调API包含某些错误的重试逻辑。在TransientTransactionError或UnknownTransactionCommitResort提交错误后,服务器尝试重新运行事务。
从MongoDB 6.2开始,如果服务器收到TransactionTooLargeForCache错误,则不会重试该事务。

重要事项

  • 推荐。使用针对该版本更新的 MongoDB 驱动程序 的 MongoDB 部署。对于MongoDB 4.2上的事务 部署(副本集和分片集群),客户端必须使用针对 MongoDB 4.2 更新的 MongoDB 驱动程序。

  • 使用驱动程序时,每个操作在 事务必须与会话相关联(即 将会话传递给每个操作)。

  • 事务使用中的操​作事务级读取 关注,事务级别 写关注和事务级读取首选项。 ​

  • 在 MongoDB 4.2 及更早版本中,您无法在 交易。导致文档插入的写入操作 (例如 或使用 ) 更新操作 如果在事务中运行,则必须位于现有集合上。insertupsert: true

  • 从MongoDB 4.4开始,您可以在 隐式或显式事务

/*
  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; e.g.
  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()
        .readPreference(ReadPreference.primary())
        .readConcern(ReadConcern.LOCAL)
        .writeConcern(WriteConcern.MAJORITY)
        .build();

/* Step 3: Define the sequence of operations to perform inside the transactions. */

TransactionBody txnBody = new TransactionBody() {
    public String execute() {
        MongoCollection coll1 = client.getDatabase("mydb1").getCollection("foo");
        MongoCollection 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();
}

 三、事务和原子性

分布式事务和多文档事务

从MongoDB 4.2开始,这两个术语是同义词。分散式 事务是指分片上的多文档事务 群集和副本集。多文档事务(是否在 分片集群或副本集)也称为分布式 从MongoDB 4.2开始的事务。

对于需要对多个读取和写入的原子性的情况 文档(在单个或多个集合中),MongoDB支持 多文档事务:

  • 在4.0版本中,MongoDB支持多文档事务 副本集。

  • 在4.2版本中,MongoDB引入了分布式事务, 增加了对分片上的多文档事务的支持 集群并包含对 副本集上的多文档事务。

    在 MongoDB 4.2 部署上使用事务(副本集和 分片集群),客户端必须使用 MongoDB 驱动程序更新为 MongoDB 4.2.

多文档事务是原子的(即提供 “全有或全无”命题):

  • 当事务提交时,事务中所做的所有数据都会更改 保存并在事务外部可见。也就是说,交易 不会在回滚其他更改时提交其某些更改。

    在事务提交之前,数据在 事务在事务之外不可见。

     然而,当一个事务写入多个碎片时,并非所有外部读取操作都需要等待提交的事务的结果在碎片中可见。例如,如果事务已提交,并且写1在分片a上可见,但写2在分片B上还不可见,则外部读取时关注点“local”可以读取写1的结果,而不会看到写2。

  • 当事务中止时,事务中所做的所有数据都会更改 被丢弃而不可见。例如,如果有 事务中的操作失败,事务中止,所有 在事务中所做的数据更改将被丢弃,而不会被丢弃 变得可见。

注意 

在大多数情况下,多文档事务会产生更大的 与单个文档写入相比,性能成本,以及 多文档事务的可用性不应是 替换有效的架构设计。在许多情况下,非规范化数据模型(嵌入式文档和数组)将继续成为您的最佳选择 数据和用例。也就是说,在许多情况下,对数据进行建模 适当地将最大限度地减少对多文档的需求 交易。 有关其他事务使用注意事项 (如运行时限制和oplog大小限制)

四、Transactions 和操作

分布式事务可以跨多个操作使用, 集合、数据库、文档,以及从 MongoDB 4.2 开始的分片。

对于事务:

  • 您可以在现有集合上指定读/写(CRUD)操作。有关CRUD操作的列表,请参阅CRUD操作。
  • 从MongoDB 4.4开始,您可以在事务中创建集合和索引。
  • 事务中使用的集合可以在不同的数据库中。
  • 您不能写入有上限的集合。(从MongoDB 4.2开始)
  • 从有上限的集合中读取时,不能使用读取关注点“快照”。(从MongoDB 5.0开始)
  • 无法读取/写入config、admin或本地数据库中的集合。
  • 无法写入system.*集合。
  • 您不能返回支持的操作的查询计划(即解释)。
  • 对于在事务外部创建的游标,不能在事务内部调用getMore。
  • 对于在事务中创建的游标,不能在事务外部调用getMore。
  • 从MongoDB 4.2开始,您不能将killCursors指定为事务中的第一个操作。

1、在事务中创建集合和索引

从MongoDB 4.4开始,您可以在多文档事务内部执行以下操作,只要该事务不是跨碎片写入事务:

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

在MongoDB 4.2及更早版本中,事务中不允许执行影响数据库目录的操作,例如创建或删除集合或索引。
在事务中创建集合时:

  • 您可以隐式创建集合,例如使用:

          针对不存在的集合的插入操作,或者

          针对不存在的集合执行upsert:true的update/findAndModify操作。

  • 您可以使用create命令或其助手db.createCollection()显式创建集合。

在事务[1]内创建索引时,要创建的索引必须位于以下任一位置:

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

[1] 您还可以对现有索引运行db.collection.createIndex()和db.collection.createIndexes()来检查是否存在。这些操作在不创建索引的情况下成功返回。

2、限制

  • 不能在跨碎片写入事务中创建新集合。例如,如果您在一个shard中写入一个现有集合,并在另一个shard中隐式创建一个集合,则MongoDB无法在同一事务中执行这两个操作。
  • 对于在事务内显式创建集合或索引,事务读取关注级别必须为“本地”。显式创建是通过:

Command

Method

​create

db.createCollection()
createIndexes

db.collection.createIndex()

db.collection.createIndexes() ​

3、Count 操作

要在事务中执行计数操作,请使用$count聚合阶段或$group(带有$sum表达式)聚合阶段。
与4.0功能兼容的MongoDB驱动程序提供了一个集合级的API countDocuments(filter,options)作为助手方法,使用带有$sum表达式的$group执行计数。4.0驱动程序已弃用count()API。
从MongoDB 4.0.3开始,mongosh提供了db.collection.countDocuments()辅助方法,该方法使用带有$sum表达式的$group来执行计数。

4、Distinct 操作

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

  • 对于未排序的集合,可以使用db.collection.distinct()方法/distinct命令以及带有$group阶段的聚合管道。
  • 对于分片集合,不能使用db.collection.distinct()方法或distinct命令。要查找分片集合的不同值,请使用带有$group阶段的聚合管道。例如:替代 db.coll.distinct("x"),使用
    db.coll.aggregate([
       { $group: { _id: null, distinctValues: { $addToSet: "$x" } } },
       { $project: { _id: 0 } }
    ])

    请使用以下内容,而不是db.coll.dinclusive(“x”,{status:“A”}):

db.coll.aggregate([
   { $match: { status: "A" } },
   { $group: { _id: null, distinctValues: { $addToSet: "$x" } } },
   { $project: { _id: 0 } }
])

执行结果:

{ "distinctValues" : [ 2, 3, 1 ] }

 5、Restricted 操作

在版本4.4中进行了更改。
交易中不允许执行以下操作:

  • 影响数据库目录的操作,例如使用MongoDB 4.2或更低版本时创建或删除集合或索引。从MongoDB 4.4开始,您可以在事务中创建集合和索引,除非事务是跨碎片写入事务。有关详细信息,请参阅在事务中创建集合和索引。
  • 在跨碎片写入事务中创建新集合。例如,如果您在一个shard中写入一个现有集合,并在另一个shard中隐式创建一个集合,则MongoDB无法在同一事务中执行这两个操作。
  • 显式创建集合,例如db.createCollection()方法,以及索引,例如db.collection.createIndexes()和db.collection.createIndex()方法。
  • listCollections和listIndexes命令及其辅助方法。
  • 其他非CRUD和非信息性操作,如createUser、getParameter、count等及其助手。

五、事务和会话

事务与会话相关联;即您启动会话的事务。
在任何给定的时间,会话最多可以有一个打开的事务。
使用驱动程序时,事务中的每个操作都必须与会话相关联。有关详细信息,请参阅您的驱动程序特定文档。
如果会话结束并且有一个打开的事务,则该事务将中止。

六、读取关注点/写入关注点/读取首选项

1、事务和读取首选项

事务中的操作使用事务级别的读取首选项。
使用驱动程序,您可以在事务开始时设置事务级别的读取首选项:

  • 如果未设置事务级别的读取首选项,则事务将使用会话级别的读取首选项。
  • 如果未设置事务级别和会话级别的读取首选项,则事务将使用客户端级别的读取首选项。默认情况下,客户端级别的读取首选项是主要的。

包含读取操作的多文档事务必须使用读取首选项主。给定事务中的所有操作都必须路由到同一成员。

2、事务和读取关注

事务中的操作使用事务级读取关注点。也就是说,在集合和数据库级别设置的任何读取关注点在事务内部都会被忽略。
您可以在事务开始时设置事务级别的读取关注点。

  • 如果未设置事务级读取关注点,则事务级 读取关注点默认为会话级读取关注点。

  • 如果未设置事务级别和会话级别读取关注点, 事务级读取关注点默认为客户端级读取 关注。默认情况下,客户端级读取关注点为“local”用于针对主数据库的读取。另请参阅:

    • 事务和读取首选项  

    • 默认 MongoDB 读取关注点/写入关注点

你可能感兴趣的:(MongoDB,mongodb,数据库,nosql)