在MongoDB中,对单个文档的操作是原子的。由于可以在单个文档结构中使用内嵌文档和数组来获得数据之间的关系,而不必跨多个文档和集合进行范式化,所以这种单文档原子性避免了许多实际场景中对多文档事务的需求。
对于那些需要对多个文档(在单个或多个集合中)进行原子性读写的场景,MongoDB支持多文档事务。而使用分布式事务,事务可以跨多个操作、集合、数据库、文档和分片使用。
➤ 使用右上角的Select your language 下拉菜单来设置以下示例的语言。
此示例突出显示了事务API的关键组件。
该示例使用新的回调API来进行事务处理,其中涉及启动事务、执行指定的操作并提交(或在出错时中止)。新的回调API还包含针对TransientTransactionError或UnknownTransactionCommitResult提交错误的重试逻辑。
重要
推荐 。使用针对MongoDB部署版本更新的MongoDB驱动程序。对于MongoDB 4.2部署(副本集和分片集群上的事务,客户端必须 使用为MongoDB 4.2更新的MongoDB驱动程序。
使用驱动程序时,事务中的每个操作必须 与会话相关联(即将会话传递给每个操作)。
在MongoDB 4.2及更早版本中,你无法在事务中创建集合。如果在事务内部运行会导致文档插入的写操作(例如insert或带有upsert: true的更新操作),必须在已存在 的集合上才能执行。
复制 static bool
with_transaction_example (bson_error_t *error)
{
mongoc_client_t *client = NULL;
mongoc_write_concern_t *wc = NULL;
mongoc_read_concern_t *rc = NULL;
mongoc_read_prefs_t *rp = NULL;
mongoc_collection_t *coll = NULL;
bool success = false;
bool ret = false;
bson_t *doc = NULL;
bson_t *insert_opts = NULL;
mongoc_client_session_t *session = NULL;
mongoc_transaction_opt_t *txn_opts = NULL;
/* For a replica set, include the replica set name and a seedlist of the
* members in the URI string; e.g.
* uri_repl = "mongodb://mongodb0.example.com:27017,mongodb1.example.com:" \
* "27017/?replicaSet=myRepl";
* client = test_framework_client_new (uri_repl);
* For a sharded cluster, connect to the mongos instances; e.g.
* uri_sharded =
* "mongodb://mongos0.example.com:27017,mongos1.example.com:27017/";
* client = test_framework_client_new (uri_sharded);
*/
client = get_client ();
/* Prereq: Create collections. */
wc = mongoc_write_concern_new ();
mongoc_write_concern_set_wmajority (wc, 1000);
insert_opts = bson_new ();
mongoc_write_concern_append (wc, insert_opts);
coll = mongoc_client_get_collection (client, "mydb1", "foo");
doc = BCON_NEW ("abc", BCON_INT32 (0));
ret = mongoc_collection_insert_one (
coll, doc, insert_opts, NULL /* reply */, error);
if (!ret) {
goto fail;
}
bson_destroy (doc);
mongoc_collection_destroy (coll);
coll = mongoc_client_get_collection (client, "mydb2", "bar");
doc = BCON_NEW ("xyz", BCON_INT32 (0));
ret = mongoc_collection_insert_one (
coll, doc, insert_opts, NULL /* reply */, error);
if (!ret) {
goto fail;
}
/* Step 1: Start a client session. */
session = mongoc_client_start_session (client, NULL /* opts */, error);
if (!session) {
goto fail;
}
/* Step 2: Optional. Define options to use for the transaction. */
txn_opts = mongoc_transaction_opts_new ();
rp = mongoc_read_prefs_new (MONGOC_READ_PRIMARY);
rc = mongoc_read_concern_new ();
mongoc_read_concern_set_level (rc, MONGOC_READ_CONCERN_LEVEL_LOCAL);
mongoc_transaction_opts_set_read_prefs (txn_opts, rp);
mongoc_transaction_opts_set_read_concern (txn_opts, rc);
mongoc_transaction_opts_set_write_concern (txn_opts, wc);
/* Step 3: Use mongoc_client_session_with_transaction to start a transaction,
* execute the callback, and commit (or abort on error). */
ret = mongoc_client_session_with_transaction (
session, callback, txn_opts, NULL /* ctx */, NULL /* reply */, error);
if (!ret) {
goto fail;
}
success = true;
fail:
bson_destroy (doc);
mongoc_collection_destroy (coll);
bson_destroy (insert_opts);
mongoc_read_concern_destroy (rc);
mongoc_read_prefs_destroy (rp);
mongoc_write_concern_destroy (wc);
mongoc_transaction_opts_destroy (txn_opts);
mongoc_client_session_destroy (session);
mongoc_client_destroy (client);
return success;
}
/* Define the callback that specifies the sequence of operations to perform
* inside the transactions. */
static bool
callback (mongoc_client_session_t *session,
void *ctx,
bson_t **reply,
bson_error_t *error)
{
mongoc_client_t *client = NULL;
mongoc_collection_t *coll = NULL;
bson_t *doc = NULL;
bool success = false;
bool ret = false;
client = mongoc_client_session_get_client (session);
coll = mongoc_client_get_collection (client, "mydb1", "foo");
doc = BCON_NEW ("abc", BCON_INT32 (1));
ret =
mongoc_collection_insert_one (coll, doc, NULL /* opts */, *reply, error);
if (!ret) {
goto fail;
}
bson_destroy (doc);
mongoc_collection_destroy (coll);
coll = mongoc_client_get_collection (client, "mydb2", "bar");
doc = BCON_NEW ("xyz", BCON_INT32 (999));
ret =
mongoc_collection_insert_one (coll, doc, NULL /* opts */, *reply, error);
if (!ret) {
goto fail;
}
success = true;
fail:
mongoc_collection_destroy (coll);
bson_destroy (doc);
return success;
} 同样请参阅:
有关mongoarrow-up-right shell中的示例,请参阅mongoShell示例arrow-up-right 。
说明
分布式事务和多文档事务
从MongoDB 4.2开始,这两个术语是同义词。分布式事务是指分片集群和副本集上的多文档事务。从MongoDB 4.2开始,多文档事务(无论是在分片集群上还是副本集上)也称为分布式事务。
对于多文档(在单个或多个集合中)读写上有原子性要求的场景,MongoDB提供了多文档事务支持:
在4.0版本 中,MongoDB支持副本集上的多文档事务。
在4.2版本 中,MongoDB引入了分布式事务,增加了对分片集群上多文档事务的支持,并合并了对副本集上多文档事务的现有支持。
为了在MongoDB 4.2部署(副本集和分片集群)上使用事务,客户端必须 使用为MongoDB 4.2更新的MongoDB驱动程序。
Multi-document transactions are atomic (i.e. provide an "all-or-nothing" proposition):
多文档事务是原子的(即提供“全有或全无”的语义):
当事务提交时,事务中所做的所有数据更改都将保存并在事务外部可见。也就是说,事务不会在回滚其他更改时提交其某些更改。
在事务提交之前,事务中所做的数据更改在事务之外是不可见的。
然而,当事务写入多个分片时,并非所有外部读取操作都需要等待已提交事务的结果在分片中可见。例如,如果事务已提交并且写入操作1在分片A上可见,但写入操作2在分片B上尚不可见,则外部读关注为"local"arrow-up-right 的读操作可以读取写入操作1的结果,看不到写入操作2。
当事务中止时,事务中所做的所有数据更改都将被丢弃,而不会变得可见。例如,如果事务中的任何操作失败,事务就会中止,并且事务中所做的所有数据更改都将被丢弃,而不会变得可见。
重要
在大多数情况下,多文档事务比单文档写入会产生更大的性能成本,并且多文档事务的可用性不应替代有效的模型设计。对于许多场景,反范式化数据模型(嵌入文档和数组)arrow-up-right 依然会是最适合你的数据和用例。也就是说,对于许多场景,适当地对数据进行建模可以最大限度地减少对多文档事务的需求。
有关其他事务使用注意事项(例如runtime限制和oplog大小限制),另请参阅生产注意事项arrow-up-right 。
TIP
同样请参阅:
Commit期间的外部读操作arrow-up-right
分布式事务可用于跨多个操作、集合、数据库、文档以及从MongoDB 4.2开始可以跨分片。
对于事务:
事务中使用的集合可以位于不同的数据库中。
提示
你不能在跨分片的写事务中创建新集合。例如,如果你想对一个分片中已存在的集合进行写入且在另外一个不同的分片中隐式地创建集合,那么MongoDB无法在同一事务中执行这两种操作。
你不能读/写config、admin或local数据库中的集合。
你不能返回这类支持操作的查询计划(即explain)。
有关事务中不支持的操作列表,请参阅受限操作arrow-up-right 。
提示
在开始事务之前立即创建或删除集合时,如果在事务内访问该集合,注意使用写关注"majority"arrow-up-right 来执行这些创建或删除操作,从而确保事务可以获取到所需要的锁。
提示
同样请参阅:
事务和操作参考arrow-up-right
从MongoDB 4.4开始,使用功能兼容性版本(fcv)arrow-up-right "4.4",可以在多文档事务arrow-up-right 中创建集合和索引,除非事务是跨分片写入事务。如果使用"4.2"或更低版本,事务中不允许 使用影响数据库目录的操作,例如创建或删除集合和索引。
当在事务内部创建一个集合时:
在事务中创建索引arrow-up-right [[1]](https: //docs.mongodb.com/manual/core/transactions/#footnote-create-existing-index),要创建的索引需满足下面两者之一情况:
限制
你不能在跨分片的写事务中创建新集合。例如,如果要对一个分片中已存在的集合执行写入操作且在另外一个不同的分片中隐式地创建集合,那么MongoDB无法在同一事务中执行这两种操作。
提示
同样请参阅:
受限制的操作arrow-up-right
要在事务中执行计数操作,请使用$countarrow-up-right 聚合阶段或带有[$sum](https ://docs.mongodb.com/manual/reference/operator/aggregation/sum/#mongodb-group-grp.-sum)表达式的$grouparrow-up-right 聚合阶段。
与4.0特性兼容的MongoDB驱动程序提供了一个集合级别的APIcountDocuments(filter, options)作为带有$sumarrow-up-right 表达式的[$group](https://docs.mongodb.com/manual/reference/operator /aggregation/group/#mongodb-pipeline-pipe.-group)的帮助函数来执行计数。4.0驱动程序已弃用count() API。
从MongoDB4.0.3开始,mongoarrow-up-right shell提供了在db.collection.countDocuments()arrow-up-right 中使用带有[$sum](https://docs.mongodb.com/ manual/reference/operator/aggregation/sum/#mongodb-group-grp.-sum)表达式的$grouparrow-up-right 来执行计数的帮助函数。
为了在事务中执行一个distinct操作:
对于未分片的集合,可以使用[db.collection.distinct()](https://docs.mongodb.com/manual/reference/method/db.collection.distinct/#mongodb-method-db.collection .distinct)方法/distinctarrow-up-right 命令以及带有$grouparrow-up-right 阶段的聚合管道。
信息类操作命令,比如helloarrow-up-right , buildInfoarrow-up-right , connectionStatusarrow-up-right (以及它们的辅助函数)是被允许在事务中使用的。然而,它们不能作为事务中的第一个操作。
在4.4版本中变更。
下列这些操作在事务中是不被允许的:
在跨分片写入事务中创建新的集合。例如,如果在一个分片中对现有集合进行写入并在不同分片中隐式创建一个集合,则MongoDB无法在同一事务中执行这两种操作。
提示
同样请参阅:
事务是与某个会话相关联的;即你为一个会话启动一个事务。
在任何给定时间,一个会话最多可以有一个打开的事务。
使用驱动程序时,事务中的每个操作都必须与会话相关联。有关详细信息,请参阅你使用的驱动程序文档。
如果一个会话结束了并且它有一个打开的事务,则事务会中止。
在事务中使用事务级读偏好arrow-up-right 的操作。
在使用驱动时,你可以在事务开始时设置事务级别的读偏好arrow-up-right :
如果事务级别的读偏好没有设置,事务会使用会话级别的读偏好。
包含读操作的多文档事务arrow-up-right 必须使用primaryarrow-up-right 读偏好。在一个给定事务中的所有操作都必须路由到同一个成员。
在事务中的操作会使用事务级读关注arrow-up-right 。也就是说,在事务内部忽略在集合和数据库级别设置的任何读关注。
可以在事务开始时设置事务级别的读关注arrow-up-right 。
如果事务级别的读关注没有设置,事务级的读关注默认为会话级的读关注。
事务支持下列的读关注级别:
"local"
"majority"
"snapshot"
事务使用事务级写关注arrow-up-right 来提交写操作。事务内的写操作必须没有显式定义写关注,并使用默认的写关注。在提交时,然后使用事务级写关注提交写入。
提示
不要为事务内的单个写操作显式设置写关注。为事务内的单个写操作设置写关注会导致错误。
可以在事务开始时设置事务级别的写关注arrow-up-right :
如果事务级别的写关注没有设置,事务级写关注默认为提交的会话级写关注。
事务支持所有写关注warrow-up-right 的值,包括:
w: 1
w: "majority"
说明
不管为事务指定的写关注arrow-up-right ,分片集群事务的提交操作包括一部分使用了{w: "majority", j: true}写关注的操作。
关于使用事务的各种生产注意事项,请参阅生产注意事项arrow-up-right 。另外,如果是分片集群,同样请参阅生产注意事项(分片集群)arrow-up-right 。
如果任何事务操作从包含仲裁节点的分片读取或写入,其写操作跨越多个分片的事务将出错并中止。
另请参阅Disabled Read Concern Majorityarrow-up-right ,了解在分片上已禁用读关注 majority的事务限制。
三成员PSA(主-从-仲裁)副本集或者拥有三成员PSA分片的分片集群可能已经禁用了读关注majority(--enableMajorityReadConcern falsearrow-up-right 或replication.enableMajorityReadConcern: falsearrow-up-right )
提示
要检查读关注"majority"是否被禁用,可以在mongodarrow-up-right 实例上运行db.serverStatus()arrow-up-right 并检查storageEngine.supportsCommittedReadsarrow-up-right 字段。如果值为false,则表示读关注"majority"已禁用。
更多信息请参考三成员PSA架构arrow-up-right 和三成员PSA分片arrow-up-right 。
不能在包含writeConcernMajorityJournalDefaultarrow-up-right 设置为false 分片的分片集群上运行事务(例如包含使用了内存存储引擎arrow-up-right 作为投票成员的分片)。
说明
不管为事务指定的写关注arrow-up-right ,分片集群事务的提交操作都会包含一部分使用了{w: "majority", j: true}写关注的操作。
MongoDB提供了多种事务相关指标:
为了使用事务,部署架构中所有成员的featureCompatibilityVersionarrow-up-right 至少为:
最小featureCompatibilityVersion
为了检查成员的FCV,连接到成员并运行下面的命令:
更多信息详见setFeatureCompatibilityVersionarrow-up-right 参考页。
从MongoDB 4.2开始,多文档事务arrow-up-right 支持副本集和分片集群,其中:
在MongoDB 4.0中,只有使用WiredTiger存储引擎的副本集支持事务。
说明
你不能在包含writeConcernMajorityJournalDefaultarrow-up-right 设置为 false 分片的分片集群上运行事务,例如包含使用了内存存储引擎arrow-up-right 作为投票成员的分片。
原文链接:https://docs.mongodb.com/manual/core/transactions/
译者:李正洋