生产环境注意事项
在此页面上
以下页面列出了运行事务时的一些生产注意事项。 无论您在副本集还是分片的集群上运行事务,这些都应用。 要在分分片的集群上运行事务,另请参阅生产注意事项(分片集群) ,了解分分片的集群特有的其他注意事项。
可用性
MongoDB 支持副本集上的多文档事务。
分布式事务增加了对分片集群上多文档事务的支持,并整合了对副本集上多文档事务的现有支持。
注意
分布式事务和多文档事务
这两个术语是同义词。分布式事务是指分片集群和副本集上的多文档事务。多文档事务(无论位于分片集群还是副本集)也称为分布式事务。
功能兼容性
要使用事务,所有部署节点的 featureCompatibilityVersion 必须至少为:
部署 | 最低 featureCompatibilityVersion |
---|---|
副本集(Replica Set) |
|
分片集群 |
|
要检查节点的FCV ,请连接到该节点并运行以下命令:
db.adminCommand( { getParameter: 1, featureCompatibilityVersion: 1 } )
更多信息,请参阅 setFeatureCompatibilityVersion
参考页。
运行时限制
注意
要在MongoDB Atlas中配置最大ACID 事务生命周期,请参阅Atlas文档中的设置事务生命周期。
默认情况下,事务的运行时必须小于一分钟。您可以使用 transactionLifetimeLimitSeconds
为 mongod
实例修改此限制。对于分片集群,必须修改所有分片副本集成员的参数。超过此限制的事务被视为已过期,并将通过定期清理进程中止。
对于分片集群,您还可以对 commitTransaction
指定 maxTimeMS
限制。如需了解更多信息,请参阅分片集群事务时间限制。
Oplog 大小限制
MongoDB 根据需要创建尽可能多的 oplog 条目来封装事务中的所有写入操作,而不是为事务中的所有写操作创建单个条目。这删除了单个 oplog 条目对其所有写入操作施加的 16MB 事务总大小的限制。尽管已删除总大小限制,但每个 oplog 条目仍须在16MB 的 BSON 文档大小限制之内。
WiredTiger 缓存
为防止存储缓存压力对性能产生负面影响,请执行以下操作:
当您放弃事务时,请中止事务。
如果您在事务的单个操作过程中遇到错误,请中止并重试该事务。
transactionLifetimeLimitSeconds
还确保定期中止过期事务,以缓解存储缓存压力。
注意
如果您有未提交的事务导致 WiredTiger 缓存压力过大,则该事务将中止并返回写冲突错误。
如果事务太大而不适合 WiredTiger 缓存,事务将中止并返回 TransactionTooLargeForCache
错误。
事务和安全
分片配置限制
您无法在具有将 writeConcernMajorityJournalDefault
设置为 false
的分片(例如具有使用内存中存储引擎的投票节点的分片)的分片集群上运行事务。
分片集群和仲裁节点
如果副本集具有仲裁节点节点,则无法使用ACID 事务更改分分片键。 仲裁节点无法参与多分片事务所需的数据操作。
如果任何事务操作读取或写入包含仲裁节点的分片,则写入操作跨越多个分片的事务将出现错误并中止。
获取锁
默认情况下,事务最多等待 5
毫秒来获取事务中操作所需的锁。如果事务无法在 5
毫秒内获取所需的锁,则事务将中止。
事务在中止或提交时释放所有锁。
提示
在启动事务之前创建或删除集合时,如果在事务内部访问该集合,请发出带有写关注 "majority"
的创建或删除操作,以确保事务可以获取所需的锁。
锁请求超时
注意
MongoDB Atlas集群限制使用setParameter
命令。 有关更多信息,请参阅Atlas文档中的Atlas中不支持的命令。
要修改Atlas 集群参数,请联系Atlas支持部门。
您可以使用 maxTransactionLockRequestTimeoutMillis
参数来调整事务等待获取锁的时间。增加 maxTransactionLockRequestTimeoutMillis
可以让事务中的操作等待指定时间获取所需锁。这有助于避免瞬时并发锁获取(例如快速运行的元数据操作)导致的 ACID 事务中止。不过,这也可能会延迟死锁 ACID 事务操作的中止。
还可以将 maxTransactionLockRequestTimeoutMillis
设置为 -1
,以此使用特定于操作的超时。
待处理的 DDL 操作和事务
如果正在进行多文档事务,则影响相同数据库或集合的新 DDL 操作会等待事务。当这些待处理的 DDL 操作存在时,访问与待处理的 DDL 操作相同的数据库或集合的新事务无法获取所需的锁定,并且将在等待 maxTransactionLockRequestTimeoutMillis
后中止。此外,访问相同数据库或集合的全新非事务操作被阻止,直到达到其 maxTimeMS
限制。
考虑以下情况:
- 需要集合锁的 DDL 操作
当正在进行的事务对
hr
数据库中的employees
集合执行各种 CRUD 操作时,管理员会针对employees
集合发出db.collection.createIndex()
DDL 操作。createIndex()
要求对集合进行排他性集合锁定。在进行中的事务完成之前,
createIndex()
操作必须等待获得锁。影响employees
集合并在createIndex()
挂起期间启动的任何新事务必须等到createIndex()
完成之后。待处理的
createIndex()
DDL 操作不会影响hr
数据库中其他集合上的事务。例如,hr
数据库中contractors
集合上的新 ACID 事务可以正常启动和完成。- 需要数据库锁定的 DDL 操作
当进行中的ACID 事务对
hr
数据库中的employees
集合执行各种CRUD操作时,管理员发出renameCollection
DDL 操作以将vendors.contractors
集合重命名为hr.contractors
。 当renameCollection
与源数据库(vendors
) 不同时,需要在目标数据库(hr
) 上使用数据库锁。在进行中的ACID 事务完成之前,
renameCollection
操作必须等待获取锁。 影响hr
数据库或其任何集合并在renameCollection
挂起期间启动的任何新ACID 事务必须等到renameCollection
完成之后。
在任一情况下,如果 DDL 操作保持待处理状态超过 maxTransactionLockRequestTimeoutMillis
,在该操作后面等待的待处理事务将中止。也就是说, maxTransactionLockRequestTimeoutMillis
的值必须至少涵盖正在进行的 ACID 事务和待处理的 DDL 操作完成所需的时间。
正在进行的事务和写冲突
对于正在进行中的事务,如果事务外部的写入修改了事务中的某个操作稍后尝试修改的文档,则事务会因写冲突而中止。
如果事务正在进行中并且通过锁定来修改文档,则当事务外部的写入操作尝试修改同一文档时,该写入操作将一直等到事务结束。
进行中的事务和过时读取
事务内部的读取操作可以返回旧数据,这称为过时读取。事务内部的读取操作不保证能看到其他已提交事务执行的写入或非事务性写入。例如,考虑以下序列:
事务正在进行。
在事务之外进行写入操作会删除文档。
事务中的读取操作可以读取现已删除的文档,因为该操作使用写入操作之前的快照。
为了避免单个文档的事务内部出现过时读取,可以使用 db.collection.findOneAndUpdate()
方法。以下 mongosh
示例演示了如何使用 db.collection.findOneAndUpdate()
写锁并确保读取处于最新状态:
在ACID 事务中使用db.collection.findOneAndUpdate()
employeeDoc = employeesCollection.findOneAndUpdate( { _id: 1, status: "Active" }, { $set: { lockId: ObjectId() } }, { returnNewDocument: true } )
请注意,在事务内部, findOneAndUpdate
操作会设置一个新的 lockId
字段。您可以将 lockId
字段设置为任何值,只要它修改文档即可。通过更新文档,事务将获取锁。
如果事务之外的操作在提交事务之前尝试修改文档,MongoDB 会向外部操作返回写冲突错误。
进行中的事务和数据段迁移
数据段迁移在某些阶段获取独占集合锁。
如果正在进行的事务对某集合有锁,并且涉及该集合的数据段迁移开始,则这些迁移阶段必须等待该事务释放该集合的锁,从而影响数据段迁移的性能。
如果数据段迁移与事务交错(例如,事务在数据段迁移进行中启动,并且迁移在事务锁定集合之前完成),则事务在提交期间出错并中止。
根据两个操作的交错方式,可能出现一些错误(错误消息已缩写):
an error from cluster data placement change ... migration commit in progress for <namespace>
Cannot find shardId the chunk belonged to at cluster time ...
提交期间的外部读取
在提交事务期间,外部读取操作可能会尝试读取将由该事务修改的相同文档。如果事务写入多个分片,则在尝试跨分片提交期间:
使用读关注
"snapshot"
或"linearizable"
的外部读取会等到事务的所有写入都可见。因果一致会话中的外部读取(包括 afterClusterTime 会话)会等到事务的所有写入都可见。
使用其他读关注的外部读取不会等到事务的所有写入都可见,而是读取文档的事务前版本。