在MongoDB中,对单个文档的操作是原子性的。因为您可以使用嵌入的文档和数组来捕获单个文档结构中的数据之间的关系,而不是跨多个文档和集合进行规范化,所以这种单文档原子性消除了对于需要对多个文档(在单个或多个集合中)进行原子性读写的情况,MongoDB支持多文档事务。使用分布式事务,可以跨多个操作、集合、数据库、文档和碎片使用事务。许多实际用例对多文档事务的需求。
1.事务API
以下示例突出显示了transactions API的关键组件:
该示例使用新的回调API处理事务,事务启动事务,执行指定的操作,并提交(或因错误中止)。新的回调API还为TransientTransactionError或UnknownTransactionCommitResult提交错误合并了重试逻辑。
重要的:
/*
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);
/*
Prereq: Create collections. CRUD operations in transactions must be on existing 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();
}
2.事务和原子性
分布式事务和多文档事务
从MongoDB 4.2开始,这两个术语是同义词。分布式事务是指sharded集群和replica sets上的多文档事务。多文档事务(不管是分片集群还是副本集)在MongoDB 4.2中也被称为分布式事务。
对于需要对多个文档进行原子性读写的情况(在单个或多个集合中),MongoDB支持多文档事务:
多文档事务是原子性的(即提供一个“全有或全无”的命题):
重要的:
在大多数情况下,与单个文档写入相比,多文档事务会带来更大的性能成本,而且多文档事务的可用性不应该代替有效的模式设计。对于许多场景,非规范化数据模型(嵌入文档和数组)对于您的数据和用例仍然是最优的。也就是说,对于许多场景,适当地对数据建模将最小化对多文档事务的需求。
有关其他事务使用注意事项(如运行时限制和oplog大小限制),请参见生产
3. 事务和操作
分布式事务可以跨多个操作、集合、数据库、文档使用,在MongoDB 4.2中可以使用碎片。
事务:
system.*
collections写入。在事务中不允许创建或删除集合或索引等影响数据库编目的操作。例如,事务不能包含会导致创建新集合的插入操作。看到限制操作。
提示:
在开始事务之前立即创建或删除集合时,如果在事务中访问了该集合,则使用write关注点“majority”发出创建或删除操作,以确保事务可以获得所需的锁。
3.1 操作数
要在事务中执行计数操作,请使用$count聚合阶段或$group(带有$sum表达式)聚合阶段。
与4.0特性兼容的MongoDB驱动程序提供了一个集合级API countDocuments(filter, options)作为一个助手方法,它使用带有$sum表达式的$group来执行计数。4.0驱动程序已经弃用了count() API。
从MongoDB 4.0.3开始,mongo shell提供了db.collection.countDocuments()帮助方法,该方法使用带有$sum表达式的$group来执行计数。
3.2 不同的操作
在事务中执行不同的操作:
要查找切分集合的不同值,请使用包含$group stage的聚合管道。例如:
db.coll.aggregate([
{ $group: { _id: null, distinctValues: { $addToSet: "$x" } } },
{ $project: { _id: 0 } }
])
db.coll.aggregate([
{ $match: { status: "A" } },
{ $group: { _id: null, distinctValues: { $addToSet: "$x" } } },
{ $project: { _id: 0 } }
])
该管道将光标返回到文档:
{ "distinctValues" : [ 2, 3, 1 ] }
迭代游标以访问结果文档。
3.3 信息操作
信息命令,如isMaster、buildInfo、connectionStatus(及其助手方法),在事务中是允许的;但是,它们不能是事务中的第一个操作。
3.4 限制操作
以下操作在交易中是不允许的:
4. 事务和会话
5. 读关注点/写关注点/读偏好
5.1 事务和读取首选项
事务中的操作使用事务级读首选项。
使用驱动程序,您可以在事务开始时设置事务级别的read首选项:
包含读操作的多文档事务必须使用读首选项主。给定事务中的所有操作都必须路由到相同的成员。
5.2 事务和读关注点
事务中的操作使用事务级读关注点。也就是说,在集合和数据库级别上的任何读关注集都将在事务内部被忽略。
您可以在事务开始时设置事务级别的读关注点。
事务支持以下读取关注级别:
"local"
"majority"
"snapshot"
事务和写事务
事务使用事务级别的写关注点提交写操作。事务中的写操作必须在没有显式写关注规范的情况下发出,并使用默认的写关注。在提交时,使用事务级别的写关注点来提交写。
提示:
不要显式地为事务中的各个写操作设置写关系。为事务中的各个写操作设置写关注点会导致错误。
您可以在事务开始时设置事务级别的写关注点:
事务支持所有写关心w值,包括:
w: 1
重要的:
当您使用w: 1提交时,如果出现故障转移,您的事务可以回滚。
w: "majority"
请注意:
不管为事务指定的写关注点是什么,sharded集群事务的提交操作都包括一些使用{w: "majority", j: true}写关注点的部分。
6. 基本信息
6.1 生产注意事项
有关使用事务的各种生产注意事项,请参阅生产注意事项。此外,或分片集群,请参见生产注意事项(分片集群)。
6.2 裁决者
如果任何事务操作读取或写入包含仲裁程序的碎片,则其写入操作跨越多个碎片的事务将出错并终止。
有关已禁用读关注多数的碎片上的事务限制,请参阅禁用读关注多数。
6.3 Disabled Read Concern Majority
一个3成员的PSA(主-次-仲裁者)副本集或一个带有3成员PSA碎片的分片集群可能已经禁用了read concern majority(——enableMajorityReadConcern false或复制)。enableMajorityReadConcern:false)
6.4 分片的集群,
readConcern level 'snapshot' is not supported in sharded clusters when enableMajorityReadConcern=false.
在副本集,
您可以指定读关心“本地”或“多数”或“快照”,即使在副本集中已禁用读关心“多数”。
但是,如果您计划使用禁用的read concern majority切分来过渡到切分集群,那么您可能希望避免使用read concern“snapshot”。
提示:
要检查是否禁用了read关注点“majority”,可以在mongod实例上运行db.serverStatus()并检查存储引擎。supportsCommittedReads字段。如为假,则阅读关系“多数”被禁用。
碎片配置限制
您不能在分片集群上运行事务,分片的writeConcernMajorityJournalDefault设置为false(例如带有使用内存存储引擎的投票成员的分片)。
请注意:
不管为事务指定的写关注点是什么,sharded集群事务的提交操作都包括一些使用{w: "majority", j: true}写关注点的部分。
诊断
MongoDB提供各种事务指标:
Via | |
---|---|
|
Returns transactions metrics. |
$currentOp aggregation pipeline |
Returns:
|
|
Returns:
|
mongod and mongos log messages |
包括关于慢速事务的信息(即超过操作概要的事务)。在TXN日志组件下。 |
功能兼容版本(FCV)
要使用事务,对于部署的所有成员的featureCompatibilityVersion必须至少:
Deployment | Minimum featureCompatibilityVersion |
---|---|
Replica Set | 4.0 |
Sharded Cluster | 4.2 |
要检查成员的fCV,请连接该成员并运行以下命令:
db.adminCommand( { getParameter: 1, featureCompatibilityVersion: 1 } )
存储引擎
从MongoDB 4.2开始,在复制集和分片集群上支持多文档事务,其中:
在MongoDB 4.0中,只有使用WiredTiger存储引擎的副本集支持事务。
请注意:
您不能在分片集群上运行事务,分片的writeConcernMajorityJournalDefault设置为false,例如带有使用内存存储引擎的投票成员的分片。
一、API驱动
1. 回调API vs核心API
回调API:
核心API:
2. 回调的API
新的回调API整合了逻辑:
重要的
该示例使用新的回调API处理事务,事务启动事务,执行指定的操作,并提交(或因错误中止)。新的回调API为“TransientTransactionError”或“UnknownTransactionCommitResult”提交错误合并了重试逻辑。
/*
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);
/*
Prereq: Create collections. CRUD operations in transactions must be on existing 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();
}
3. 使用核心接口
核心事务API不包含错误重试逻辑标签:
下面的例子合并了逻辑,以对暂时错误重试事务,对未知的提交错误重试提交:
重要的:
要将读写操作与事务关联起来,必须将会话传递给事务中的每个操作。
void runTransactionWithRetry(Runnable transactional) {
while (true) {
try {
transactional.run();
break;
} catch (MongoException e) {
System.out.println("Transaction aborted. Caught exception during transaction.");
if (e.hasErrorLabel(MongoException.TRANSIENT_TRANSACTION_ERROR_LABEL)) {
System.out.println("TransientTransactionError, aborting transaction and retrying ...");
continue;
} else {
throw e;
}
}
}
}
void commitWithRetry(ClientSession clientSession) {
while (true) {
try {
clientSession.commitTransaction();
System.out.println("Transaction committed");
break;
} catch (MongoException e) {
// can retry commit
if (e.hasErrorLabel(MongoException.UNKNOWN_TRANSACTION_COMMIT_RESULT_LABEL)) {
System.out.println("UnknownTransactionCommitResult, retrying commit operation ...");
continue;
} else {
System.out.println("Exception during commit ...");
throw e;
}
}
}
}
void updateEmployeeInfo() {
MongoCollection employeesCollection = client.getDatabase("hr").getCollection("employees");
MongoCollection eventsCollection = client.getDatabase("reporting").getCollection("events");
TransactionOptions txnOptions = TransactionOptions.builder()
.readPreference(ReadPreference.primary())
.readConcern(ReadConcern.MAJORITY)
.writeConcern(WriteConcern.MAJORITY)
.build();
try (ClientSession clientSession = client.startSession()) {
clientSession.startTransaction(txnOptions);
employeesCollection.updateOne(clientSession,
Filters.eq("employee", 3),
Updates.set("status", "Inactive"));
eventsCollection.insertOne(clientSession,
new Document("employee", 3).append("status", new Document("new", "Inactive").append("old", "Active")));
commitWithRetry(clientSession);
}
}
void updateEmployeeInfoWithRetry() {
runTransactionWithRetry(this::updateEmployeeInfo);
}
4.驱动程序版本
对于MongoDB 4.2部署(副本集和分片集群)上的事务,客户端必须使用针对MongoDB 4.2更新的MongoDB驱动程序:
|
|
|
对于MongoDB 4.0复制集上的事务,客户端需要为MongoDB 4.0或更高版本更新MongoDB驱动程序。
|
|
|
5.交易错误处理
无论数据库系统是MongoDB还是关系数据库,应用程序都应该采取措施来处理事务提交期间的错误,并合并事务的重试逻辑。
"TransientTransactionError"
不管retrywrite的值是多少,事务中的各个写操作都是不可重试的。如果操作遇到与“TransientTransactionError”标签相关的错误,例如主步骤停止时,可以重试整个事务。
"UnknownTransactionCommitResult"
提交操作是可重试的写操作。如果提交操作遇到错误,MongoDB驱动程序会不考虑retrywrite的值重试提交。
如果提交操作遇到一个标记为“UnknownTransactionCommitResult”的错误,可以重试提交。
驱动程序版本错误
在有多个mongos实例的切分集群上,使用更新为MongoDB 4.0(而不是MongoDB 4.2)的驱动程序执行事务将会失败,并可能导致错误,包括:
请注意:
你的驱动程序可能会返回一个不同的错误。详情请参阅您的司机的文件。
Error Code | Error Message |
---|---|
251 | cannot continue txnId -1 for session ... with txnId 1 |
50940 | cannot commit with no participants |
对于MongoDB 4.2部署(副本集和分片集群)上的事务,使用针对MongoDB 4.2更新的MongoDB驱动程序
6.附加信息
6.1 mongo
Shell Example
以下的mongo shell方法可用于事务:
Session.startTransaction()
Session.commitTransaction()
Session.abortTransaction()
请注意:
为了简单起见,mongo shell示例省略了重试逻辑和健壮的错误处理。有关在应用程序中合并事务的更实际示例,请参见事务错误处理。
// Prereq: Create collections. CRUD operations in transactions must be on existing collections.
db.getSiblingDB("mydb1").foo.insert( {abc: 0}, { writeConcern: { w: "majority", wtimeout: 2000 } } );
db.getSiblingDB("mydb2").bar.insert( {xyz: 0}, { writeConcern: { w: "majority", wtimeout: 2000 } } );
// Start a session.
session = db.getMongo().startSession( { readPreference: { mode: "primary" } } );
coll1 = session.getDatabase("mydb1").foo;
coll2 = session.getDatabase("mydb2").bar;
// Start a transaction
session.startTransaction( { readConcern: { level: "local" }, writeConcern: { w: "majority" } } );
// Operations inside the transaction
try {
coll1.insertOne( { abc: 1 } );
coll2.insertOne( { xyz: 999 } );
} catch (error) {
// Abort transaction on error
session.abortTransaction();
throw error;
}
// Commit the transaction using write concern set at transaction start
session.commitTransaction();
session.endSession();
二、生产注意事项
下面的页面列出了运行事务时的一些生产注意事项。无论您在复制集还是分片集群上运行事务,这些都适用。要在分片集群上运行事务,请参阅生产注意事项(分片集群),以了解特定于分片集群的其他注意事项。
1.可用性
要在MongoDB 4.2部署(副本集和分片集群)上使用事务,客户端必须使用针对MongoDB 4.2更新的MongoDB驱动程序。
分布式事务和多文档事务
从MongoDB 4.2开始,这两个术语是同义词。分布式事务是指sharded集群和replica sets上的多文档事务。多文档事务(不管是分片集群还是副本集)在MongoDB 4.2中也被称为分布式事务。
2. 特征相容
要使用事务,对于部署的所有成员的featureCompatibilityVersion必须至少:
Deployment | Minimum featureCompatibilityVersion |
---|---|
Replica Set | 4.0 |
Sharded Cluster | 4.2 |
要检查成员的fCV,请连接该成员并运行以下命令:
db.adminCommand( { getParameter: 1, featureCompatibilityVersion: 1 } )
3.运行时限制
默认情况下,事务的运行时间必须少于一分钟。您可以使用transactionLifetimeLimitSeconds为mongod实例修改这个限制。对于分片集群,必须修改所有分片复制集成员的参数。超过此限制的事务将被视为已过期,并将被定期清理进程中止。
对于分片集群,您还可以在commitTransaction上指定maxTimeMS限制。有关更多信息,请参见切分集群事务的时间限制。
4.Oplog大小限制
从4.2版开始,
MongoDB创建尽可能多的oplog条目来封装一个事务中的所有写操作,而不是为事务中的所有写操作创建一个条目。这消除了单个oplog条目对其所有写操作施加的事务的16MB总大小限制。虽然取消了总大小限制,但是每个oplog条目仍然必须在16MB的BSON文档大小限制之内。
在version 4.0中,
如果事务包含任何写操作,MongoDB在提交时创建一个oplog(操作日志)条目。也就是说,事务中的各个操作没有对应的oplog条目。相反,一个oplog条目包含一个事务中的所有写操作。事务的oplog条目必须在16MB的BSON文档大小限制内。
5.WiredTiger缓存
为了防止存储缓存压力对性能产生负面影响:
transactionLifetimeLimitSeconds还确保定期中止过期的事务,以减轻存储缓存的压力。
6.事务和安全
7. 碎片配置限制
您不能在分片集群上运行事务,分片的writeConcernMajorityJournalDefault设置为false(例如带有使用内存存储引擎的投票成员的分片)。
8.切分集群和仲裁程序
如果任何事务操作读取或写入包含仲裁程序的碎片,则其写入操作跨越多个碎片的事务将出错并终止。
请参阅3个成员的主-副-仲裁架构,了解对已禁用read关注多数的碎片的事务限制。
9. 三人Primary-Secondary-Arbiter架构
对于具有主-副-仲裁(PSA)体系结构的三成员副本集或具有三成员PSA碎片的分片集群,您可能已经禁用了read concern“majority”以避免缓存压力。
分片的集群,
readConcern level 'snapshot' is not supported in sharded clusters when enableMajorityReadConcern=false.
在副本集,
您可以指定读关心“本地”或“多数”或“快照”,即使在副本集中已禁用读关心“多数”。
但是,如果您计划使用禁用的read concern majority切分来过渡到切分集群,那么您可能希望避免使用read concern“快照”。
提示:
要检查是否禁用了read关注点“majority”,可以在mongod实例上运行db.serverStatus()并检查存储引擎。supportsCommittedReads字段。如为假,则阅读关系“多数”被禁用。
10.获取锁
默认情况下,事务要等待5毫秒才能获得事务中操作所需的锁。如果事务无法在5毫秒内获得所需的锁,则事务将中止。
事务在中止或提交时释放所有锁。
提示:
在开始事务之前立即创建或删除集合时,如果在事务中访问了该集合,则使用write关注点“majority”发出创建或删除操作,以确保事务可以获得所需的锁。
10.1 锁请求超时
您可以使用maxTransactionLockRequestTimeoutMillis参数来调整事务等待获取锁的时间。增加maxTransactionLockRequestTimeoutMillis允许事务中的操作等待指定的时间来获取所需的锁。这可以帮助避免临时并发锁获取(如快速运行的元数据操作)时的事务中止。但是,这可能会延迟死锁事务操作的中止。
您还可以通过将maxTransactionLockRequestTimeoutMillis设置为-1来使用特定于操作的超时。
11.挂起的DDL操作和事务
如果一个多文档事务正在进行中,则影响相同数据库或集合的新DDL操作将在事务之后等待。虽然存在这些挂起的DDL操作,但是访问与挂起的DDL操作相同的数据库或集合的新事务无法获得所需的锁,并且将在等待maxTransactionLockRequestTimeoutMillis之后中止。此外,访问相同数据库或集合的新非事务操作将阻塞,直到达到maxTimeMS限制为止。
考虑以下场景:
需要集合锁的DDL操作
当一个正在进行的事务对hr数据库中的employees集合执行各种CRUD操作时,管理员对employees集合发出db.collection.createIndex() DDL操作。createIndex()要求集合上有一个独占的集合锁。
在进行中的事务完成之前,createIndex()操作必须等待以获取锁。任何影响employees集合并在createIndex()挂起时启动的新事务必须等到createIndex()完成之后。
挂起的createIndex() DDL操作不会影响hr数据库中其他集合上的事务。例如,hr数据库中关于承包方收集的新事务可以正常启动和完成。
需要数据库锁的DDL操作
当一个正在进行的事务对hr数据库中的employees集合执行各种CRUD操作时,管理员会对相同数据库中的contractor集合发出collMod DDL操作。collMod需要在父hr数据库上有一个数据库锁。
在进行中的事务完成之前,collMod操作必须等待以获取锁。当collMod挂起时,任何影响hr数据库或其集合的新事务都必须等待,直到collMod完成。
在这两种情况下,如果DDL操作的挂起时间超过maxTransactionLockRequestTimeoutMillis,则挂起的事务将在该操作中止后等待。也就是说,maxTransactionLockRequestTimeoutMillis的值必须至少覆盖进行中的事务和挂起的DDL操作完成所需的时间。
12. 正在进行的事务和写冲突
如果一个事务正在进行中,而事务外部的写操作修改了一个文档,而事务中的某个操作稍后试图修改该文档,则事务将由于写冲突而中止。
如果一个事务正在进行中,并且使用了一个锁来修改文档,那么当事务外部的写操作试图修改相同的文档时,写操作将一直等待,直到事务结束。
13. 正在进行的事务和过时的读取
事务中的读操作可以返回过时的数据。也就是说,事务中的读操作不能保证看到其他已提交事务或非事务性写操作执行的写操作。例如,考虑以下顺序:1)事务正在进行中;2)事务外部的写操作删除文档;3)事务内部的读操作能够读取已删除的文档,因为操作使用的是写之前的快照。
为了避免在单个文档的事务中进行过时的读取,可以使用db.collection.findOneAndUpdate()方法。例如:
session.startTransaction( { readConcern: { level: "snapshot" }, writeConcern: { w: "majority" } } );
employeesCollection = session.getDatabase("hr").employees;
employeeDoc = employeesCollection.findOneAndUpdate(
{ _id: 1, employee: 1, status: "Active" },
{ $set: { employee: 1 } },
{ returnNewDocument: true }
);
14.正在进行的事务和块迁移
块迁移在某些阶段需要独占收集锁。
如果一个正在进行的事务对一个集合有一个锁,并且涉及该集合的块迁移已经启动,那么这些迁移阶段必须等待事务释放对集合的锁,从而影响块迁移的性能。
如果一个块迁移与一个事务交错(例如,如果一个事务在块迁移已经开始的时候启动,并且在事务对集合进行锁定之前完成迁移),则提交和中止期间的事务错误。
根据这两个操作的交错方式,一些示例错误包括(错误消息已被缩写):
15. 提交期间的外部读取
在事务的提交期间,外部读取操作可能尝试读取将被事务修改的相同文档。如果事务写入多个碎片,那么在跨碎片的提交尝试期间
16.错误
16.1 使用MongoDB 4.0驱动程序
要在MongoDB 4.2部署(副本集和分片集群)上使用事务,客户端必须使用针对MongoDB 4.2更新的MongoDB驱动程序。
在有多个mongos实例的切分集群上,使用更新为MongoDB 4.0(而不是MongoDB 4.2)的驱动程序执行事务将会失败,并可能导致错误,包括:
请注意:
你的驱动程序可能会返回一个不同的错误。详情请参阅您的司机的文件。
Error Code | Error Message |
---|---|
251 | cannot continue txnId -1 for session ... with txnId 1 |
50940 | cannot commit with no participants |
三、生产注意事项(分片集群)
从4.2版开始,MongoDB提供了为切分集群执行多文档事务的能力。
下面的页面列出了特定于在分片集群上运行事务的关注点。这些问题是在生产考虑中所列之外的。
1.分片事务和MongoDB驱动程序
对于MongoDB 4.2部署(副本集和分片集群)上的事务,客户端必须使用针对MongoDB 4.2更新的MongoDB驱动程序。
在有多个mongos实例的切分集群上,使用更新为MongoDB 4.0(而不是MongoDB 4.2)的驱动程序执行事务将会失败,并可能导致错误,包括:
请注意:
你的驱动程序可能会返回一个不同的错误。详情请参阅您的司机的文件。
Error Code | Error Message |
---|---|
251 | cannot continue txnId -1 for session ... with txnId 1 |
50940 | cannot commit with no participants |
2. Performance
单一的碎片
针对单个碎片的事务应该具有与复制集事务相同的性能。
多个碎片
影响多个碎片的事务会导致更大的性能成本。
请注意:
在分片集群中,如果任何涉及的分片包含仲裁程序,则跨多个分片的事务将出错并终止。
时间限制
要指定时间限制,请指定commitTransaction上的maxTimeMS限制。
如果maxTimeMS未指定,MongoDB将使用transactionLifetimeLimitSeconds。
如果指定了maxTimeMS,但是会导致事务超过transactionLifetimeLimitSeconds, MongoDB将使用transactionLifetimeLimitSeconds。
要修改切分集群的transactionLifetimeLimitSeconds,必须修改所有碎片复制集成员的参数。
3.阅读问题
多文档事务支持“本地”、“多数”和“快照”读取关注级别。
对于分片集群上的事务,只有“快照”读关注提供了跨多个分片的一致快照。
有关读取关注点和事务的更多信息,请参见事务和读取关注点。
4.写问题
您不能在分片集群上运行事务,分片的writeConcernMajorityJournalDefault设置为false(例如带有使用内存存储引擎的投票成员的分片)。
请注意:
不管为事务指定的写关注点是什么,sharded集群事务的提交操作都包括一些使用{w: "majority", j: true}写关注点的部分。
如果任何事务操作读取或写入包含仲裁程序的碎片,则其写入操作跨越多个碎片的事务将出错并终止。
请参阅三个成员主-副-仲裁碎片,了解对碎片的事务限制,这些碎片已禁用了read concern majority。
6.三个成员的主要-次要-仲裁碎片
对于包含三个成员的PSA碎片的分片集群,您可能已经禁用了read关注点“多数”(例如——enableMajorityReadConcern false或复制)。enableMajorityReadConcern: false)来避免缓存压力。
分片的集群,
readConcern level 'snapshot' is not supported in sharded clusters when enableMajorityReadConcern=false.
检查读关心“多数”是否被禁用,
您可以运行db.serverStatus()并检查存储引擎。supportsCommittedReads字段。如为假,则阅读关系“多数”被禁用。
7.备份和恢复
警告:
对于正在处理切分事务的4.2+切分集群,mongodump和mongorestore不能作为备份策略的一部分,因为用mongodump创建的备份不能维护跨切分事务的原子性保证。
对于包含正在进行的分片事务的4.2+分片集群,使用以下协调的备份和恢复进程之一,这些进程确实维护了分片事务的原子性保证:
8. 块迁移
块迁移在某些阶段需要独占收集锁。
如果一个正在进行的事务对一个集合有一个锁,并且涉及该集合的块迁移已经启动,那么这些迁移阶段必须等待事务释放对集合的锁,从而影响块迁移的性能。
如果一个块迁移与一个事务交错(例如,如果一个事务在块迁移已经开始的时候启动,并且在事务对集合进行锁定之前完成迁移),则提交和中止期间的事务错误。
根据这两个操作的交错方式,一些示例错误包括(错误消息已被缩写):
9.提交期间的外部读取
在事务的提交期间,外部读取操作可能尝试读取将被事务修改的相同文档。如果事务写入多个碎片,那么在跨碎片的提交尝试期间
10.与复制索引构建的交互
对于集合上的复制索引构建(与滚动索引构建相反),一旦针对主复制集成员发出的索引构建完成,次要成员将应用相关的oplog条目并开始索引构建。在构建索引时,次要服务器等待应用任何后续的oplog条目,包括在构建期间提交的分布式事务。如果复制停止的时间比oplog窗口的时间长,则次要服务器将失去同步,需要重新同步才能恢复。
为了最小化分片事务和索引之间的潜在交互,请考虑以下在分片集群上构建索引的策略之一:
四、事务和操作
事务:
可以在现有集合上指定读/写(CRUD)操作。集合可以在不同的数据库中。有关CRUD操作的列表,请参阅CRUD操作。
在多文档事务中不允许创建或删除集合或索引等影响数据库编目的操作。例如,多文档事务不能包含会导致创建新集合的插入操作。看到限制操作。
1.多文档事务中支持的操作
CRUD操作
以下的读/写操作在事务中是允许的:
Method | Command | Note |
---|---|---|
db.collection.aggregate() |
aggregate |
不包括以下阶段:
|
db.collection.countDocuments() |
排除以下查询操作符表达式:
该方法为查询使用$match聚合阶段,为执行计数使用$sum表达式的$group聚合阶段。 |
|
db.collection.distinct() |
distinct |
可在未分片的集合上使用。 对于切分集合,使用$group阶段的聚合管道。看到不同的操作。 |
db.collection.find() |
find |
|
geoSearch |
||
|
delete |
|
|
findAndModify |
对于upsert,仅在对现有集合运行时使用。 |
|
insert |
仅当对现有集合运行时。 |
db.collection.save() |
如果是插入,则仅在对现有集合运行时使用。 | |
|
update |
对于upsert,仅在对现有集合运行时使用。 |
Various Bulk Operation Methods |
对于插入操作,仅在对现有集合运行时使用。 对于upsert,仅在对现有集合运行时使用。 |
更新碎片键值
从MongoDB 4.2开始,您可以通过在事务中或以retryable write的形式发出单文档update/findAndModify操作来更新文档的碎片键值(除非碎片键字段是不可变的_id字段)。有关详细信息,请参见更改文档的碎片键值。
操作数
要在事务中执行计数操作,请使用$count聚合阶段或$group(带有$sum表达式)聚合阶段。
与4.0特性兼容的MongoDB驱动程序提供了一个集合级API countDocuments(filter, options)作为一个助手方法,它使用带有$sum表达式的$group来执行计数。4.0驱动程序已经弃用了count() API。
从MongoDB 4.0.3开始,mongo shell提供了db.collection.countDocuments()帮助方法,该方法使用带有$sum表达式的$group来执行计数。
不同的操作
在事务中执行不同的操作:
要查找切分集合的不同值,请使用包含$group stage的聚合管道。例如:
db.coll.aggregate([
{ $group: { _id: null, distinctValues: { $addToSet: "$x" } } },
{ $project: { _id: 0 } }
])
db.coll.aggregate([
{ $match: { status: "A" } },
{ $group: { _id: null, distinctValues: { $addToSet: "$x" } } },
{ $project: { _id: 0 } }
])
该管道将光标返回到文档:
{ "distinctValues" : [ 2, 3, 1 ] }
迭代游标以访问结果文档。
信息操作
信息命令,如isMaster、buildInfo、connectionStatus(及其助手方法),在事务中是允许的;但是,它们不能是事务中的第一个操作。
限制操作
以下操作在交易中是不允许的: