事务
在MongoDB中,对单个文档的操作是原子的。由于可以在单个文档结构中使用内嵌文档和数组来获得数据之间的关系,而不必跨多个文档和集合进行范式化,所以这种单文档原子性避免了许多实际场景中对多文档事务的需求。
对于那些需要对多个文档(在单个或多个集合中)进行原子性读写的场景,MongoDB支持多文档事务。而使用分布式事务,事务可以跨多个操作、集合、数据库、文档和分片使用。
此示例突出显示了事务 API 的关键组件。
该示例使用新的回调API来进行事务处理,其中涉及启动事务、执行指定的操作并提交(或在出错时中止)。新的回调API还包含针对TransientTransactionError或UnknownTransactionCommitResult提交错误的重试逻辑。
重要
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;
}
同样请参阅:
有关mongo
shell中的示例,请参阅mongo
Shell示例。
说明
分布式事务和多文档事务
从MongoDB 4.2开始,这两个术语是同义词。分布式事务是指分片集群和副本集上的多文档事务。从MongoDB 4.2开始,多文档事务(无论是在分片集群上还是副本集上)也称为分布式事务。
对于多文档(在单个或多个集合中)读写上有原子性要求的场景,MongoDB提供了多文档事务支持:
Multi-document transactions are atomic (i.e. provide an "all-or-nothing" proposition):
多文档事务是原子的(即提供“全有或全无”的语义):
重要
在大多数情况下,多文档事务比单文档写入会产生更大的性能成本,并且多文档事务的可用性不应替代有效的模型设计。对于许多场景,反范式化数据模型(嵌入文档和数组) 依然会是最适合你的数据和用例。也就是说,对于许多场景,适当地对数据进行建模可以最大限度地减少对多文档事务的需求。
有关其他事务使用注意事项(例如runtime限制和oplog大小限制),另请参阅生产注意事项。
同样请参阅:
分布式事务可用于跨多个操作、集合、数据库、文档以及从MongoDB 4.2开始可以跨分片。
对于事务:
有关事务中不支持的操作列表,请参阅受限操作。
提示
在开始事务之前立即创建或删除集合时,如果在事务内访问该集合,注意使用写关注"majority"来执行这些创建或删除操作,从而确保事务可以获取到所需要的锁。
提示
同样请参阅:
从MongoDB 4.4开始,使用功能兼容性版本(fcv)"4.4",可以在多文档事务中创建集合和索引,除非事务是跨分片写入事务。如果使用"4.2"或更低版本,事务中不允许使用影响数据库目录的操作,例如创建或删除集合和索引。
当在事务内部创建一个集合时:
在事务中创建索引[[1]](https: //docs.mongodb.com/manual/core/transactions/#footnote-create-existing-index),要创建的索引需满足下面两者之一情况:
[1] | 你还可以对现有索引运行 [db.collection.createIndex()]和 [db.collection.createIndexes()]来检查是否存在。这些操作会成功地返回且不会创建索引。 |
---|---|
你不能在跨分片的写事务中创建新集合。例如,如果你想对一个分片中已存在的集合进行写入且在另外一个不同的分片中隐式地创建集合,那么 MongoDB 无法在同一事务中执行这两种操作。
要在事务内显式创建集合或索引,事务读关注级别必须为["local"
]。
显式创建是通过:
命令 | Method |
---|---|
[create] | [db.createCollection()] |
[createIndexes] | [db.collection.createIndex()] |
shouldMultiDocTxnCreateCollectionAndIndexes
]必须为 true
(默认值)。在对分片集群设置参数时,请在所有分片上设置该参数。也可以参考:
受限制的操作
要在事务中执行计数操作,请使用 [count]聚合阶段 或 [group](带有 [
表达式)聚合阶段。与 4.0 特性兼容的 MongoDB 驱动程序提供了一个集合级别的 API countDocuments(filter, options) 作为使用 [group]带有 [sum]表达式来执行计数。4.0 驱动程序已弃用 count() API。
从 MongoDB 4.0.3 开始,[mongo
]
shell 提供了在 [db.collection.countDocuments()]中使用 [group]的带有 [sum]表达式来执行计数的帮助命令。
操作
为了在事务中执行一个 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.distinct("x", { status: "A" })
,请使用:
db.coll.aggregate([
{ $match: { status: "A" } },
{ $group: { _id: null, distinctValues: { $addToSet: "$x" } } },
{ $project: { _id: 0 } }
])
管道将游标返回到文档:
{ "distinctValues" : [ 2, 3, 1 ] }
迭代游标来访问结果集文档。
在4.4版本中变更。
下列这些操作在事务中是不被允许的:
提示
同样请参阅:
在事务中使用事务级读偏好的操作。
在使用驱动时,你可以在事务开始时设置事务级别的读偏好:
包含读操作的多文档事务必须使用primary读偏好。在一个给定事务中的所有操作都必须路由到同一个成员。
在事务中的操作会使用事务级读关注。也就是说,在事务内部忽略在集合和数据库级别设置的任何读关注。
可以在事务开始时设置事务级别的读关注。
事务支持下列的读关注级别:
"local"
事务使用事务级写关注来提交写操作。事务内的写操作必须没有显式定义写关注,并使用默认的写关注。在提交时,然后使用事务级写关注提交写入。
提示
不要为事务内的单个写操作显式设置写关注。为事务内的单个写操作设置写关注会导致错误。
可以在事务开始时设置事务级别的写关注:
事务支持所有写关注w的值,包括:
w: 1
w: 1
会在提交已经被应用到主节点后反馈确认结果。
重要
当使用w: 1
提交,事务在发生故障时可能会回滚。w: 1
写关注提交,事务级"majority"
的读关注无法保证事务中的读操作能读取大多数已提交的数据。w: 1
写关注提交,事务级"snapshot"
的读关注无法保证事务中的读操作能使用大多数已提交数据的快照。
w: "majority"w: "majority"
会在提交的数据被应用到大多数(M)有投票权的成员后返回确认;即提交数据已被应用到主节点和(M-1)有投票权的从节点。w: "majority"
写关注提交时,事务级w: "majority"
读关注可以确保操作能读取到大多数已提交的数据。对于分片集群上的事务,这种大多数已提交数据的视图在分片之间不会同步。w: "majority"
写关注提交时,事务级"snapshot"
读关注可以确保证操作能获取来自大多数已提交数据的同步快照。说明
不管为事务指定的写关注,分片集群事务的提交操作包括一部分使用了{w: "majority", j: true}
写关注的操作。
关于使用事务的各种生产注意事项,请参阅生产注意事项。另外,如果是分片集群,同样请参阅生产注意事项(分片集群)。
如果任何事务操作从包含仲裁节点的分片读取或写入,其写操作跨越多个分片的事务将出错并中止。
另请参阅Disabled Read Concern Majority,了解在分片上已禁用读关注majority的事务限制。
三成员PSA(主-从-仲裁)副本集或者拥有三成员PSA分片的分片集群可能已经禁用了读关注majority(--enableMajorityReadConcern false或replication.enableMajorityReadConcern: false)
提示
要检查读关注"majority"是否被禁用,可以在mongod实例上运行db.serverStatus()并检查storageEngine.supportsCommittedReads字段。如果值为false,则表示读关注"majority"已禁用。
不能在包含writeConcernMajorityJournalDefault设置为false 分片的分片集群上运行事务(例如包含使用了内存存储引擎作为投票成员的分片)。
说明
不管为事务指定的写关注,分片集群事务的提交操作都会包含一部分使用了{w: "majority", j: true}写关注的操作。
MongoDB提供了多种事务相关指标:
返回transactions 相关指标。
为了使用事务,部署架构中所有成员的featureCompatibilityVersion至少为:
4.0
为了检查成员的FCV,连接到成员并运行下面的命令:
db.adminCommand( { getParameter: 1, featureCompatibilityVersion: 1 } )
更多信息详见setFeatureCompatibilityVersion参考页。
从MongoDB 4.2开始,多文档事务支持副本集和分片集群,其中:
在MongoDB 4.0中,只有使用WiredTiger存储引擎的副本集支持事务。
说明
你不能在包含writeConcernMajorityJournalDefault设置为 false 分片的分片集群上运行事务,例如包含使用了内存存储引擎作为投票成员的分片。
本文分享自 Mongoing中文社区 微信公众号,前往查看
如有侵权,请联系 cloudcommunity@tencent.com 删除。
本文参与 腾讯云自媒体同步曝光计划 ,欢迎热爱写作的你一起参与!