Docs 菜单

Docs 主页开发应用程序MongoDB Manual

生产环境注意事项

在此页面上

  • 可用性
  • 功能兼容性
  • 运行时限制
  • Oplog 大小限制
  • WiredTiger 缓存
  • 事务和安全
  • 分片配置限制
  • 分片集群和仲裁节点
  • 获取锁
  • 待处理的 DDL 操作和事务
  • 正在进行的事务和写冲突
  • 进行中的事务和过时读取
  • 进行中的事务和数据段迁移
  • 提交期间的外部读取
  • 错误
  • 更多信息

以下页面列出了运行事务时的一些生产注意事项。无论您在副本集还是分片集群上运行事务,这些都适用。要在分片集群上运行事务,另请参阅生产注意事项(分片集群),了解分片集群的其他特定注意事项。

  • 在版本 4.0 中,MongoDB 支持副本集上的多文档事务。

  • 在版本 4.2 中,MongoDB 引入分布式事务,增加对分片集群上多文档事务的支持,并合并对副本集上文档事务的现有支持。

    要在 MongoDB 4.2 部署(副本集和分片集群)上使用事务,客户端 必须使用针对 MongoDB 4.2 更新的 MongoDB 驱动程序。

注意

分布式事务和多文档事务

从 MongoDB 4.2 开始,这两个术语属于同义词。分布式事务是指分片集群和副本集上的多文档事务。从 MongoDB 4.2 开始,多文档事务(无论位于分片集群还是副本集)也称为“分布式事务”。

要使用事务,所有部署节点的 featureCompatibilityVersion 必须至少为:

部署
最低 featureCompatibilityVersion
副本集(Replica Set)
4.0
分片集群
4.2

要检查成员的 FCV,请连接到该成员并运行以下命令:

db.adminCommand( { getParameter: 1, featureCompatibilityVersion: 1 } )

更多信息,请参阅 setFeatureCompatibilityVersion 参考页。

默认情况下,事务的运行时必须小于一分钟。您可以使用 transactionLifetimeLimitSecondsmongod 实例修改此限制。对于分片集群,必须修改所有分片副本集成员的参数。超过此限制的事务被视为已过期,并将通过定期清理进程中止。

对于分片集群,您还可以对 commitTransaction 指定 maxTimeMS 限制。如需了解更多信息,请参阅分片集群事务时间限制

从 4.2 版本开始,
MongoDB 根据需要创建尽可能多的 oplog 条目来封装事务中的所有写入操作,而不是为事务中的所有写操作创建单个条目。这删除了单个 oplog 条目对其所有写入操作施加的 16MB 事务总大小的限制。尽管已删除总大小限制,但每个 oplog 条目仍须在16MB 的 BSON 文档大小限制之内。
在 4.0 版本中,
如果事务包含任何写入操作,则 MongoDB 会在提交时创建单个 oplog(操作日志)条目。也就是说,事务中的各个操作没有对应的 oplog 条目。相反,单个 oplog 条目包含事务中的所有写入操作。事务的 oplog 条目必须在 16 MB 的 BSON 文档大小限制之内。

为防止存储缓存压力对性能产生负面影响,请执行以下操作:

  • 当您放弃事务时,请中止事务。

  • 如果您在事务的单个操作过程中遇到错误,请中止并重试该事务。

transactionLifetimeLimitSeconds 还确保定期中止过期事务,以缓解存储缓存压力。

注意

如果您有未提交的事务导致 WiredTiger 缓存压力过大,则该事务将中止并返回写冲突错误。

如果事务太大而不适合 WiredTiger 缓存,事务将中止并返回 TransactionTooLargeForCache 错误。

  • 如果使用访问控制模式运行,则必须具有事务中操作特权

  • 如果以审核方式运行,中止的事务中的操作仍会被审核。但是,没有审核事件表明事务已中止。

您无法在具有将 writeConcernMajorityJournalDefault 设置为 false 的分片(例如具有使用内存中存储引擎的投票节点的分片)的分片集群上运行事务。

如果任何事务操作读取或写入包含仲裁节点的分片,则写入操作跨越多个分片的事务将出现错误并中止。

默认情况下,事务最多等待 5 毫秒来获取事务中操作所需的锁。如果事务无法在 5 毫秒内获取所需的锁,则事务将中止。

事务在中止或提交时释放所有锁。

提示

在启动事务之前创建或删除集合时,如果在事务内部访问该集合,请发出带有写关注 "majority" 的创建或删除操作,以确保事务可以获取所需的锁。

您可以使用 maxTransactionLockRequestTimeoutMillis 参数来调整事务等待获取锁的时间。增加 maxTransactionLockRequestTimeoutMillis 可以让事务中的操作等待指定时间获取所需锁。这有助于避免瞬时并发锁获取(例如快速运行的元数据操作)导致的 ACID 事务中止。不过,这也可能会延迟死锁 ACID 事务操作的中止。

还可以将 maxTransactionLockRequestTimeoutMillis 设置为 -1,以此使用特定于操作的超时。

如果正在进行多文档事务,则影响相同数据库或集合的新 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 操作

当进行中的事务对 hr 数据库中的 employees 集合执行各种增删改查操作时,管理员会对同一数据库中的 contractors 集合发出 collMod DDL 操作。collMod 需要针对 hr 父数据库的数据库锁。

在进行中的事务完成之前,collMod 操作必须等待获得锁。影响 hr 数据库或其任何集合并在 collMod 挂起期间启动的任何新事务必须等到 collMod 完成之后。

在任一情况下,如果 DDL 操作保持待处理状态超过 maxTransactionLockRequestTimeoutMillis ,在该操作后面等待的待处理事务将中止。也就是说, maxTransactionLockRequestTimeoutMillis 的值必须至少涵盖正在进行的 ACID 事务待处理的 DDL 操作完成所需的时间。

提示

另请参阅:

  • 正在进行的事务和写冲突

  • 进行中的事务和过时读取

  • 哪些管理命令锁定数据库?

  • 哪些管理命令锁定集合?

对于正在进行中的事务,如果事务外部的写入修改了事务中的某个操作稍后尝试修改的文档,则事务会因写冲突而中止。

如果事务正在进行中并且通过锁定来修改文档,则当事务外部的写入操作尝试修改同一文档时,该写入操作将一直等到事务结束。

事务内的读取操作可能会返回旧数据,这称为过时读取。不保证事务内的读取操作能看到其他已提交的事务或非事务写入执行的写入。例如,考虑以下序列:

  1. 事务正在进行中。

  2. 事务外部的写入会删除文档。

  3. 事务中的读取操作可以读取现已删除的文档,因为该操作使用写入操作之前的快照。

为避免单个文档的事务内部出现过时读取,您可以使用db.collection.findOneAndUpdate()方法。以下mongosh示例演示了如何使用db.collection.findOneAndUpdate()获取写锁并确保读取是最新的:

1
db.getSiblingDB("hr").employees.insertOne(
{ _id: 1, status: "Active" }
)
2
session = db.getMongo().startSession( { readPreference: { mode: "primary" } } )
3
session.startTransaction( { readConcern: { level: "snapshot" }, writeConcern: { w: "majority" } } )
employeesCollection = session.getDatabase("hr").employees
4
employeeDoc = employeesCollection.findOneAndUpdate(
{ _id: 1, status: "Active" },
{ $set: { lockId: ObjectId() } },
{ returnNewDocument: true }
)

请注意,在事务内部, findOneAndUpdate操作会设置一个新的lockId字段。您可以将lockId字段设置为任何值,只要它会修改文档即可。通过更新文档,事务获取了锁。

如果事务外部的操作尝试在提交事务之前修改文档,则 MongoDB 将向外部操作返回写冲突错误。

5
session.commitTransaction()

提交事务后,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 会话)会等到事务的所有写入都可见。

  • 使用其他读关注的外部读取不会等到事务的所有写入都可见,而是读取文档的事务前版本。

要在 MongoDB 4.2 部署(副本集和分片集群)上使用事务,客户端必须使用针对 MongoDB 4.2 更新的 MongoDB 驱动程序。

在具有多个 mongos 实例的分片集群上,使用为 MongoDB 4.0(而非 MongoDB 4.2)更新的驱动程序执行事务将会失败,并可能导致错误,包括:

注意

驱动程序可能返回不同的错误。有关详细信息,请参阅驱动程序文档。

错误代码
错误消息
251
cannot continue txnId -1 for session ... with txnId 1
50940
cannot commit with no participants
← 驱动程序 API