进入MongoDB中文手册(4.2版本)目录
1 回调API vs 核心API
回调API:
- 启动事务,执行指定的操作,然后提交(或因错误中止)
- 自动包括“ TransientTransactionError”和 “ UnknownTransactionCommitResult”的错误处理逻辑。
核心API:
- 需要显式调用才能启动事务并提交交易。
- 没有包含“ TransientTransactionError”和 “ UnknownTransactionCommitResult”的错误处理逻辑,而是提供灵活性,包含这些错误的自定义错误处理逻辑。
2 回调API
新的回调API包含逻辑:
- 如果事务遇到“ TransientTransactionError”,请重试整个事务 。
- 如果提交遇到“ UnknownTransactionCommitResult”,请重试提交操作 。
重要
- 对于MongoDB 4.2上部署(副本集和分片群集)的事务,客户端必须使用更新到MongoDB 4.2的MongoDB驱动程序。
- 使用驱动程序时,事务中的每个操作必须与会话关联(即,将会话传递给每个操作)。
以下示例使用新的回调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<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();
}
3 核心API
核心事务API不包含针对标记为以下错误的重试逻辑:
- “TransientTransactionError”。如果事务中的操作返回标有 “ TransientTransactionError”的错误,则可以重试整个事务。
要处理“ TransientTransactionError”,应用程序应明确包含错误的重试逻辑。 - “ UnknownTransactionCommitResult”。如果提交返回标有 “ UnknownTransactionCommitResult”的错误,则可以重试该提交。
要处理“ UnknownTransactionCommitResult”,应用程序应显式包含错误的重试逻辑。
以下示例包括了临时错误重试事务和针对未知提交错误重试提交的逻辑:
重要
要将读写操作与事务相关联,必须将会话传递给事务中的每个操作。
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<Document> employeesCollection = client.getDatabase("hr").getCollection("employees");
MongoCollection<Document> 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驱动程序:
- C 1.15.0
- C# 2.9.0
- Go 1.1
- Java 3.11.0
- Node 3.3.0
- Perl 2.2.0
- Python 3.9.0
- Ruby 2.10.0
- Scala 2.7.0
对于MongoDB 4.0副本集上的事务,客户端将MongoDB驱动程序更新到MongoDB 4.0或更高版本。
- Java 3.8.0
- Python 3.7.0
- C 1.11.0
- C# 2.7
- Node 3.1.0
- Ruby 2.6.0
- Perl 2.0.0
- PHP (PHPC) 1.5.0
- Scala 2.4.0
无论数据库系统是MongoDB还是关系数据库,应用程序都应采取措施处理事务提交期间的错误,并包含事务的重试逻辑。
"TransientTransactionError"
事务中的单个写操作不重试,无论retryWrites的值是多少。如果某个操作遇到与标签相关的错误,“TransientTransactionError”,例如当主操作降级时,则可以重试整个事务。
- 回调AP包括了"TransientTransactionError"的重试逻辑 。
- 核心事务API不包含"TransientTransactionError"的重试逻辑。为了处理 “TransientTransactionError”,应用程序应明确包含错误的重试逻辑。
"UnknownTransactionCommitResult"
提交操作是可重试的写操作。如果提交操作遇到错误,则MongoDB驱动程序将重试提交,而不考虑retryWrites的值 。
如果提交操作遇到标记为 "UnknownTransactionCommitResult"的错误,则可以重试提交。
- 回调API包含了的 "UnknownTransactionCommitResult"重试逻辑。
- 核心事务API不包含"UnknownTransactionCommitResult"的重试逻辑 。为了处理 “UnknownTransactionCommitResult”,应用程序应明确包含错误的重试逻辑。
5 事务错误处理
在具有多个mongos实例的分片群集上,使用更新到MongoDB 4.0(而非MongoDB 4.2)的驱动程序执行事务将失败,并可能导致错误,包括:
注意
您的驱动程序可能会返回其他错误。有关详细信息,请参阅驱动程序的文档。
错误代码 | 错误信息 |
---|---|
251 | cannot continue txnId -1 for session … with txnId 1 |
50940 | cannot commit with no participants |
对于MongoDB 4.2部署(副本集和分片群集)上的事务,请使用更新到MongoDB 4.2的MongoDB驱动程序。
6 其他信息
mongo Shell 示例
以下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();