Docs 主页 → 开发应用程序 → MongoDB Manual
事务
在 MongoDB 中,对单个文档的操作具有原子性。由于您可以使用嵌入式文档和数组来捕获单个文档结构中数据之间的关系,而无需跨多个文档和集合进行标准化,因此这种单文档原子性消除了许多实际使用案例使用分布式事务的必要性。
对于需要对多个文档(在单个或多个集合中)的读写操作具有原子性的情况,MongoDB 支持多文档事务。利用分布式事务,可以跨多个操作、集合、数据库、文档和分片使用事务。
事务 API
➤ 使用右上角的选择语言下拉菜单来设置以下示例的语言。
提示
另请参阅:
有关 mongosh
中的示例,请参阅mongosh
示例。
事务和原子性
对于需要对多个文档(在单个或多个集合中)原子性读取和写入的情况,MongoDB 支持分布式事务,包括副本集和分片集群上的事务。
分布式事务具有原子性:
事务要么应用所有数据更改,要么回滚更改。
在事务提交时,事务中所做的所有数据更改都会保存,并且在事务之外可见。
在事务进行提交前,在事务中所做的数据更改在事务外不可见。
不过,当事务写入多个分片时,并非所有外部读取操作都需等待已提交事务的结果在各个分片上可见。例如,如果事务已提交并且写入 1 在分片 A 上可见,但写入 2 在分片 B 上尚不可见,则读关注
"local"
处的外部读取可以在不看到写入 2 的情况下读取写入 1 的结果。事务中止后,在事务中所做的所有数据更改会被丢弃且不会变得可见。例如,如果事务中的任何操作失败,事务就会中止,事务中所做的所有数据更改将被丢弃且不会变得可见。
重要
在大多数情况下,与单文档写入操作相比,分布式事务会产生更高的性能成本,并且分布式事务的可用性不应取代有效的模式设计。在许多情况下,非规范化数据模型(嵌入式文档和数组)仍然是数据和使用案例的最佳选择。换言之,对于许多场景,适当的数据建模将最大限度地减少对分布式事务的需求。
有关其他事务使用注意事项(如运行时间限制和 oplog 大小限制),另请参阅生产注意事项。
提示
另请参阅:
事务和操作
可以跨多个操作、集合、数据库、文档和分片使用分布式事务。
对于事务:
您可以在事务中创建集合和索引。有关详细信息,请参阅在事务中创建集合和索引
事务中使用的集合可以位于不同的数据库中。
注意
您无法在跨分片写事务中创建新集合。例如,如果您在一个分片中写入一个现有集合,并在另一个分片中隐式创建一个集合,MongoDB 将无法在同一事务中执行这两个操作。
不能写入固定大小集合。
从固定大小集合读取时不能使用读关注
"snapshot"
。(从 MongoDB 5.0 开始)不能在
config
、admin
或local
数据库中读取/写入集合。不能写入
system.*
集合。不能使用
explain
或类似命令返回受支持操作的查询计划。
您不能将
killCursors
指定为事务中的第一个操作。
有关事务中不支持的操作列表,请参阅受限操作。
提示
在启动事务之前创建或删除集合时,如果在事务内部访问该集合,请发出带有写关注 "majority"
的创建或删除操作,以确保事务可以获取所需的锁。
提示
另请参阅:
在事务中创建集合和索引
如果事务不是跨分片写事务,则可以在分布式事务中执行以下操作:
创建集合。
在先前同一事务中创建的新空集合上创建索引。
在事务中创建集合时:
您可以隐式创建一个集合,例如:
对不存在的集合进行插入操作,或
对不存在的集合使用
upsert: true
进行 update/findAndModify 操作。
您可以使用
create
命令或其辅助程序db.createCollection()
显式创建集合。
在事务内创建索引 [1] 时,要创建的索引必须位于以下位置之一:
不存在的集合。集合作为操作的一部分创建。
先前在同一事务中创建的新空集合。
[1] | 您还可以对现有索引运行 db.collection.createIndex() 和 db.collection.createIndexes() 以检查其是否存在。这些操作成功返回而不创建索引。 |
限制
您无法在跨分片写事务中创建新集合。例如,如果您在一个分片中写入一个现有集合,并在另一个分片中隐式创建一个集合,MongoDB 将无法在同一事务中执行这两个操作。
当以分片集合为目标时,您无法在事务中使用
$graphLookup
阶段。要在事务内显式创建集合或索引,事务读关注级别必须为
"local"
。要显式创建集合和索引,请使用以下命令和方法:
提示
另请参阅:
计数操作
要在事务内执行计数操作,请使用 $count
聚合阶段或 $group
(带有 $sum
表达式)聚合阶段。
MongoDB 驱动程序提供集合级 API countDocuments(filter, options)
作为辅助方法,该方法使用 $group
和 $sum
表达式来执行计数。count()
API 已被弃用。
mongosh
提供了db.collection.countDocuments()
辅助方法,该方法使用$group
和$sum
表达式来执行计数。
去重操作
如要在事务中执行不同的操作:
对于未分片的集合,可以使用
db.collection.distinct()
方法/distinct
命令以及带有$group
阶段的聚合管道。对于分片集合,不能使用
db.collection.distinct()
方法或distinct
命令。要查找分片集合的不同值,请改用带有
$group
阶段的 aggregation pipeline。例如:不使用
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 ] } 迭代游标以访问结果文档。
信息操作
事务中允许使用诸如 hello
、buildInfo
、connectionStatus
(及其辅助方法)之类的信息命令,但它们不能是事务中的第一项操作。
限制性操作
事务中不允许执行以下操作:
在跨分片写事务中创建新集合。例如,如果您在一个分片中写入一个现有集合,并在另一个分片中隐式创建一个集合,那么 MongoDB 将无法在同一事务中执行这两项操作。
使用
"local"
以外的读关注级别时,显式创建集合(例如db.createCollection()
方法)和索引(例如db.collection.createIndexes()
和db.collection.createIndex()
方法)。listCollections
和listIndexes
命令及其辅助方法。其他非 CRUD 和非信息性操作(例如
createUser
、getParameter
和count
)及其辅助程序。
提示
另请参阅:
事务和会话
事务与会话关联。
一个会话一次最多可以具有一个未结事务。
使用驱动程序时,事务中的每项操作都必须与会话关联。有关详细信息,请参阅驱动程序特定文档。
如果会话结束并且具有打开的事务,则事务将中止。
读关注/写关注/读取偏好
事务和读取偏好
事务中的操作使用事务级读取偏好。
使用驱动程序,您可以在事务启动时设置事务级读取偏好:
如果未设置事务级别的读取偏好,则事务将使用会话级别的读取偏好。
如果未设置事务级别和会话级别的读取偏好,则事务将使用客户端级别的读取偏好。默认情况下,客户端级别的读取偏好为
primary
。
事务和读关注
事务中的操作使用事务级读关注。也就是说,在集合和数据库级别设置的任何读关注在事务中都会被忽略。
您可以在事务启动时设置事务级别的读关注。
如果未设置事务级别的读关注,则事务级别的读关注默认为会话级别的读关注。
如果未设置事务级读关注和会话级读关注,则事务级读关注默认为客户端级读关注。默认情况下,对于主节点上的读取,客户端级读关注是
"local"
。另请参阅:
事务支持以下读关注级别:
"local"
"majority"
如果事务在写关注为 "majority"的情况下提交,则读关注
"majority"
将返回已得到大多数副本集成员确认且无法回滚的数据。否则,读关注"majority"
不保证读操作读取多数提交数据。对于分片集群上的事务,读关注
"majority"
无法保证数据来自跨分片的同一快照视图。如果需要快照隔离,请使用读关注"snapshot"
。
"snapshot"
如果事务未使用写关注“多数”进行提交,则
"snapshot"
读关注无法保证读操作使用多数已提交数据的快照。对于分片集群上的事务,数据的
"snapshot"
视图会在各分片之间同步。
事务和写关注
事务使用事务级写关注来提交写入操作。事务内的写入操作必须在没有明确写关注规范的情况下执行,并须使用默认的写关注。在提交时,使用事务级写关注来提交写入。
提示
请勿为事务中的各个写入操作显式设置写关注。为事务内的各个写入操作设置写关注会返回错误消息。
您可以在事务启动时设置事务级写关注。
如果未设置事务级别的写关注,则事务级别的写关注默认为提交的会话级别写关注。
如果未设置事务级别的写关注和会话级别的的写关注,则事务级别的写关注默认为 的客户端级别的写关注,
w: "majority"
(在 MongoDB 5.0 及更高版本中),包含仲裁节点的部署有所不同。请参阅隐式默认写关注。
提示
另请参阅:
事务支持所有写关注 w 值,包括:
w: 1
写关注
w: 1
会在提交应用于主节点后返回确认信息。重要
使用
w: 1
提交时,如果发生故障转移,则可以回滚事务。使用
w: 1
写入关注提交时,事务级"majority"
读关注无法保证事务中的读操作会读取大多数已提交数据。使用
w: 1
写关注提交时,事务级"snapshot"
读关注无法保证事务中的读操作会使用大多数已提交数据的快照。
w: "majority"
在将提交应用于大多数投票节点后,写关注
w: "majority"
会返回确认消息。使用
w: "majority"
写关注提交时,事务级"majority"
读关注可以保证操作已读取大多数已提交数据。对于分片集群上的事务,大多数已提交数据的视图不会在各分片之间同步。使用
w: "majority"
写关注提交时,事务级"snapshot"
读关注可以保证操作已从大多数已提交数据的同步快照中读取。
注意
无论为事务指定哪个写关注,分片集群事务的提交操作都包含使用{w:
"majority", j: true}
写关注的某些部分。
服务器参数 coordinateCommitReturnImmediatelyAfterPersistingDecision
可控制何时将事务提交决策返回给客户端。
该参数是在 MongDB 5.0 中引入的,默认值为 true
。。在 MongoDB 6.1 中,该默认值更改为 false
。
当coordinateCommitReturnImmediatelyAfterPersistingDecision
为false
时,分片事务协调器会等待所有成员确认多文档事务提交,然后再将提交决策返回给客户端。
如果您为"majority"
多文档事务 指定 写关注,并且该事务无法复制到 计算出的多数 副本集 成员,则该事务可能不会立即回滚副本集成员。副本集 最终将保持一致 。事务始终会在所有副本集节点上应用或回滚。
无论 为事务指定哪个写关注 ,驱动程序都会在重试 时应用w: "majority"
commitTransaction
作为写关注。
基本信息
以下各节介绍事务的其他注意事项。
生产环境注意事项
有关生产环境中的事务,请参阅生产环境注意事项。此外,有关分片集群,请参阅生产环境注意事项(分片集群)。
仲裁节点
如果任何事务操作读取或写入包含仲裁节点的分片,则写入操作跨越多个分片的事务将出现错误并中止。
分片配置限制
您无法在具有将 writeConcernMajorityJournalDefault
设置为 false
的分片(例如具有使用内存中存储引擎的投票节点的分片)的分片集群上运行事务。
注意
无论为事务指定哪个写关注,分片集群事务的提交操作都包含使用{w:
"majority", j: true}
写关注的某些部分。
诊断
要获取事务状态和指标,请使用以下方法:
源 | 返回: |
---|---|
返回事务指标。 | |
$currentOp 聚合管道 | 返回:
|
currentOp 命令 | 返回:
|
在 TXN 日志组件中包含有关慢速事务(即超过 operationProfiling.slowOpThresholdMs 阈值的事务)的信息。 |
特征兼容性版本 (FCV)
要使用事务,所有部署节点的 featureCompatibilityVersion 必须至少为:
部署 | 最低 featureCompatibilityVersion |
---|---|
副本集(Replica Set) | 4.0 |
分片集群 | 4.2 |
要检查成员的 FCV,请连接到该成员并运行以下命令:
db.adminCommand( { getParameter: 1, featureCompatibilityVersion: 1 } )
更多信息,请参阅 setFeatureCompatibilityVersion
参考页。
存储引擎
副本集和分片集群支持分布式事务,其中:
主节点使用 WiredTiger 存储引擎,而
从节点使用 WiredTiger 存储引擎或内存存储引擎。
注意
您无法在具有将 writeConcernMajorityJournalDefault
设置为 false
的分片(例如具有使用内存中存储引擎的投票成员的分片)的分片集群上运行事务。
限制关键部分等待时间
从 MongoDB 5.2(和 5.0.4)开始:
要限制分片在事务中等待关键部分的时间,使用
metadataRefreshInTransactionMaxWaitBehindCritSecMS
参数。