MongoDB事务
在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;
}
同样请参阅:
说明
分布式事务和多文档事务
从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"
的读操作可以读取写入操作1的结果,看不到写入操作2。 - 当事务中止时,事务中所做的所有数据更改都将被丢弃,而不会变得可见。例如,如果事务中的任何操作失败,事务就会中止,并且事务中所做的所有数据更改都将被丢弃,而不会变得可见。
重要
在大多数情况下,多文档事务比单文档写入会产生更大的性能成本,并且多文档事务的可用性不应替代有效的模型设计。对于许多场景,反范式化数据模型(嵌入文档和数组) 依然会是最适合你的数据和用例。也就是说,对于许多场景,适当地对数据进行建模可以最大限度地减少对多文档事务的需求。
TIP
同样请参阅:
分布式事务可用于跨多个操作、集合、数据库、文档以及从MongoDB 4.2开始可以跨分片。
对于事务:
- 事务中使用的集合可以位于不同的数据库中。提示你不能在跨分片的写事务中创建新集合。例如,如果你想对一个分片中已存在的集合进行写入且在另外一个不同的分片中隐式地创建集合,那么MongoDB无法在同一事务中执行这两种操作。
- 你不能读/写
config
、admin
或local
数据库中的集合。 - 你不能写
system.*
集合。 - 你不能返回这类支持操作的查询计划(即
explain
)。
提示
提示
同样请参阅:
从MongoDB 4.4开始,使用功能兼容性版本(fcv)
"4.4"
,可以在多文档事务中创建集合和索引,除非事务是跨分片写入事务。如果使用"4.2"
或更低版本,事务中不允许使用影响数据库目录的操作,例如创建或删除集合和索引。当在事务内部创建一个集合时:
在事务中创建索引[[1]](https: //docs.mongodb.com/manual/core/transactions/#footnote-create-existing-index),要创建的索引需满足下面两者之一情况:
- 一个不存在的集合。集合的创建是作为操作的一部分。
- 先前在同一事务中创建的新空集合。
| |
限制
- 你不能在跨分片的写事务中创建新集合。例如,如果要对一个分片中已存在的集合执行写入操作且在另外一个不同的分片中隐式地创建集合,那么MongoDB无法在同一事务中执行这两种操作。
提示
同样请参阅:
与4.0特性兼容的MongoDB驱动程序提供了一个集合级别的API
countDocuments(filter, options)
作为带有$sum
表达式的[$group
](https://docs.mongodb.com/manual/reference/operator /aggregation/group/#mongodb-pipeline-pipe.-group)的帮助函数来执行计数。4.0驱动程序已弃用count()
API。从MongoDB4.0.3开始,
mongo
shell提供了在db.collection.countDocuments()
中使用带有[$sum
](https://docs.mongodb.com/ manual/reference/operator/aggregation/sum/#mongodb-group-grp.-sum)表达式的$group
来执行计数的帮助函数。为了在事务中执行一个distinct操作:
- 替代
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版本中变更。
下列这些操作在事务中是不被允许的:
- 影响数据库catalog的操作,例如在创建或删除集合和索引时使用
"4.2"
或更低的功能兼容版本(fcv)。使用fcv"4.4"
或更高版本,可以在事务中创建集合和索引,除非事务是跨分片写入事务。有关详细信息,请参阅在事务中创建集合和索引。 - 在跨分片写入事务中创建新的集合。例如,如果在一个分片中对现有集合进行写入并在不同分片中隐式创建一个集合,则MongoDB无法在同一事务中执行这两种操作。
- 显式创建集合,例如
db.createCollection()
方法和索引,例如db.collection.createIndexes()
和db. collection.createIndex()
方法,当使用"local"
以外的读取关注级别时。
提示
同样请参阅:
- 事务是与某个会话相关联的;即你为一个会话启动一个事务。
- 在任何给定时间,一个会话最多可以有一个打开的事务。
- 使用驱动程序时,事务中的每个操作都必须与会话相关联。有关详 细信息,请参阅你使用的驱动程序文档。
- 如果一个会话结束了并且它有一个打开的事务,则事务会中止。
- 如果事务级别的读偏好没有设置,事务会使用 会话级别的读偏好。
- 如果事务级别的读关注没有设置,事务级的读关注默认为会话级的读关注。
事务支持下列的读关注级别:
"local"
- 从MongoDB 4.4开始,使用功能兼容版本(fcv)
"4.4"
或更高,可以在事务内创建集合和索引。如果显式地创建集合或索引,事务必须使用读关注"local"
。隐式地创建集合可使用任何适用于事务的读关注。
"majority"
"snapshot"
提示
不要为事务内的单个写操作显式设置写关注。 为事务内的单个写操作设置写关注会导致错误。
- 如果事务级别的写关注没有设置,事务级写关注默认为提交的会话级写关注。
w: 1
w: "majority"
说明
如果任何事务操作从包含仲裁节点的分片读取或写入,其写操作跨越多个分片的事务将出错并中止。
三成员PSA(主-从-仲裁)副本集或者拥有三成员PSA分片的分片集群可能已经禁用了读关注majority(
--enableMajorityReadConcern false
或replication.enableMajorityReadConcern: false
)- 在分片集群上,如果事务涉及到具有禁用读关注“majority”的分片,则不能对事务使用读关注
"snapshot"
。你只能对事务使用读关注"local"
或者"majority"
。如果使用读关注"snapshot"
,则事务会报错并中止。readConcern level 'snapshot' is not supported in sharded clusters when enableMajorityReadConcern=false
。如果事务的任何读取或写入操作涉及已禁用读关注"majority"
的分片,则其跨越多个分片进行写入操作的事务会出错并中止。 - 在副本集上,可以定义读关注
"local"
、"majority"
或者甚至在已禁用读关注"majority"的副本集上使用"snapshot"
。但是,如果你计划迁移到有分片禁用读关注majority的分片集群上,可能希望避免使用读关注"snapshot"
。
提示
要检查读关注"majority"是否被禁用,可以在
mongod
实例上运行db.serverStatus()
并检查storageEngine.supportsCommittedReads
字段。如果值为false
,则表示读关注"majority"已禁用。说明
MongoDB提供了多种事务相关指标:
Source | Returns |
---|---|
如果操作作为事务的一部分则返回: $currentOp.transaction 。持有锁的非活动会话的信息会作为事务的一部分。$currentOp.twoPhaseCommitCoordinator 是写入多个分片的分片事务的指标。 | |
部署架构 | 最小 featureCompatibilityVersion |
副本集 | 4.0 |
分片集群 | 4.2 |
为了检查成员的FCV,连接到成员并运行下面的命令:
db.adminCommand( { getParameter: 1, featureCompatibilityVersion: 1 } )
- 主节点使用WiredTiger存储引擎,同时
在MongoDB 4.0中,只有使用WiredTiger存储引擎的副本集支持事务。
说明
原文链接:https://docs.mongodb.com/manual/core/transactions/
译者:李正洋
最近更新 1yr ago