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