在MongoDB中,对单个文档的操作是原子操作。因为您可以使用嵌入式文档和数组来捕获单个文档结构中数据之间的关系,而不是跨多个文档和集合进行规范化,所以这种单个文档原子性消除了许多实际用例对多文档事务的需要。
对于需要对多个文档(在单个或多个集合中)进行原子性读写的情况,MongoDB支持多文档事务。对于分布式事务,事务可以跨多个操作、集合、数据库、文档和碎片使用。
此示例突出显示了事务API的关键组件。特别是,它使用回调API。回调API:
回调API包含某些错误的重试逻辑。在TransientTransactionError或UnknownTransactionCommitResort提交错误后,服务器尝试重新运行事务。
从MongoDB 6.2开始,如果服务器收到TransactionTooLargeForCache错误,则不会重试该事务。
重要事项
推荐。使用针对该版本更新的 MongoDB 驱动程序 的 MongoDB 部署。对于MongoDB 4.2上的事务 部署(副本集和分片集群),客户端必须使用针对 MongoDB 4.2 更新的 MongoDB 驱动程序。
使用驱动程序时,每个操作在 事务必须与会话相关联(即 将会话传递给每个操作)。
事务使用中的操作事务级读取 关注,事务级别 写关注和事务级读取首选项。
在 MongoDB 4.2 及更早版本中,您无法在 交易。导致文档插入的写入操作 (例如 或使用 ) 更新操作 如果在事务中运行,则必须位于现有集合上。
insert
upsert: 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大小限制)
分布式事务可以跨多个操作使用, 集合、数据库、文档,以及从 MongoDB 4.2 开始的分片。
对于事务:
从MongoDB 4.4开始,您可以在多文档事务内部执行以下操作,只要该事务不是跨碎片写入事务:
在MongoDB 4.2及更早版本中,事务中不允许执行影响数据库目录的操作,例如创建或删除集合或索引。
在事务中创建集合时:
针对不存在的集合的插入操作,或者
针对不存在的集合执行upsert:true的update/findAndModify操作。
在事务[1]内创建索引时,要创建的索引必须位于以下任一位置:
[1] 您还可以对现有索引运行db.collection.createIndex()和db.collection.createIndexes()来检查是否存在。这些操作在不创建索引的情况下成功返回。
Command |
Method |
---|---|
create |
db.createCollection() |
createIndexes | db.collection.createIndex() db.collection.createIndexes() |
要在事务中执行计数操作,请使用$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来执行计数。
要在事务中执行不同的操作:
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 ] }
在版本4.4中进行了更改。
交易中不允许执行以下操作:
事务与会话相关联;即您启动会话的事务。
在任何给定的时间,会话最多可以有一个打开的事务。
使用驱动程序时,事务中的每个操作都必须与会话相关联。有关详细信息,请参阅您的驱动程序特定文档。
如果会话结束并且有一个打开的事务,则该事务将中止。
事务中的操作使用事务级别的读取首选项。
使用驱动程序,您可以在事务开始时设置事务级别的读取首选项:
包含读取操作的多文档事务必须使用读取首选项主。给定事务中的所有操作都必须路由到同一成员。
事务中的操作使用事务级读取关注点。也就是说,在集合和数据库级别设置的任何读取关注点在事务内部都会被忽略。
您可以在事务开始时设置事务级别的读取关注点。
如果未设置事务级读取关注点,则事务级 读取关注点默认为会话级读取关注点。
如果未设置事务级别和会话级别读取关注点, 事务级读取关注点默认为客户端级读取 关注。默认情况下,客户端级读取关注点为“local”用于针对主数据库的读取。另请参阅:
事务和读取首选项
默认 MongoDB 读取关注点/写入关注点