MongoDB 8.0——事务
事务
- 1、事务基础原理
- 2、驱动程序API
- 2.1、回调API
- 2.2、核心API
- 2.3、事务错误处理
- 2.3.1、TransientTransactionError
- 2.3.2、UnknownTransactionCommitResult
- 2.3.3、TransactionTooLargeForCache
- 3、事务与操作
- 3.1、事务操作基础
- 3.2、在事务中创建集合和索引
- 3.3、计数 限制性与去重操作
- 4、读取偏好与读写关注
- 4.1、事务和读取偏好
- 4.2、事务和读关注
- 4.3、事务和写关注
事务是传统数据库所具备的一项基本能力,其根本目的是为数据的可靠性和一致性提供保障。在通常的实现中,事务包含一个系列的数据库读写操作,这些操作要么全部完成,要么全部撤销。例如,在电子商城场景中,当顾客下单购买某件商品时,除生成订单外,还应该同时扣减商品的库存,这些操作应该被作为一个整体的执行单元进行处理,否则会产生数据不一致的情况。
在MongoDB数据库中,对单个文档的操作具有原子性。因为在单个文档结构中,使用内嵌文档和数据可以获得数据之间的关系,所以不必跨多个文档和集合进行范式化,这种结构特性避免了在大多数场景中对多文档事务的需求。
1、事务基础原理
MongoDB数据支持对多个文档(无论是单个集合还是多个集合)的读写操作具有原子性,并且支持多文档(分布式)的事务。利用分布式事务,可以跨多个操作、集合、数据库、文档和分片执行事务。
在下面的代码示例中,将重点介绍事务API的关键组件,其使用了回调API。回调API主要包括以下步骤:
步骤1:启动事务。
步骤2:执行指定操作。
步骤3:提交结果(或在出错时中止)。
注意:回调API包含特定错误的重试逻辑。示例代码如下:
上述代码的解释参见代码中的注释,这里就不展开了。
MongoDB数据库的事务和原子性,支持对多个文档(无论是单个集合还是多个集合)进行原子性读取和写入的情况。MongoDB支持分布式事务,包括副本集和分片集群上的事务。
关于分布式事务的原子性说明如下:
- 事务要么应用所有数据更改,要么回滚(Callback)更改。
- 在提交事务时,事务中所进行的所有数据更改都会保存,并且在事务之外可见。
- 在提交事务前,在事务中所进行的数据更改在事务外不可见。
不过,当事务写入多个分片时,并非所有外部读取操作都需等待已提交事务的结果在各个分片上可见。当事务中止后,在事务中所进行的所有数据更改都会被丢弃,并且不会变得可见。事务中的任何操作失败,事务都会中止,事务中所进行的所有数据更改将被丢弃,并且不会变得可见。
在大多数情况下,与单文档写入操作相比,分布式事务会产生更高的性能成本,并且分布式事务的可用性不应取代有效的模式设计。在许多情况下,非规范化数据模型(嵌入式文档和数组)仍然是数据和使用案例的最佳选择。换言之,对于许多场景,适当的数据建模将最大限度地减少对分布式事务的需求。
2、驱动程序API
MongoDB数据库事务的驱动程序API主要包括回调(Callback)API、核心(Core)API以及事务错误处理等方面的内容。
2.1、回调API
MongoDB数据库的回调API(Callback API)的主要功能是启动事务、执行指定操作并提交(或在出错时中止)。回调API自动包含TransientTransactionError和UnknownTransactionCommitResult的错误处理逻辑。
回调API主要包含以下逻辑:
- 如果事务遇到TransientTransactionError错误,则将事务作为一个整体进行重试。
- 如果提交操作遇到UnknownTransactionCommitResult错误,则重试提交操作。
从MongoDB 6.2版本开始,服务器在收到TransactionTooLargeForCache错误时不会重试事务。
在下面的代码示例中,使用新的回调API来处理事务,具体操作是启动事务、执行指定操作并提交(或在出错时中止)。新的回调API包含针对TransientTransactionError或UnknownTransactionCommitResult提交错误的重试逻辑。具体代码如下:
有关上述代码的说明,可参见代码中的注释。
2.2、核心API
MongoDB数据库的核心API的主要功能是需要显式调用以启动并提交事务。核心API不包含对TransientTransactionError和UnknownTransactionCommitResult的错误处理逻辑,而是提供对这些错误进行自定义错误处理的灵活性。
核心API不包含标记为以下错误的重试逻辑。
TransientTransactionError:如果事务中的操作返回标记为TransientTransactionError的错误,则可以将事务作为一个整体进行重试。如果要处理TransientTransactionError,则应用程序应显式包含该错误的重试逻辑。UnknownTransactionCommitResult:如果提交返回标记为UnknownTransactionCommit Result的错误,则可以重试提交。如果要处理UnknownTransactionCommitResult,则应用程序应显式包含该错误的重试逻辑。
在下面的代码示例中,包含在出现暂时性错误时重试事务的逻辑,以及在出现未知提交错误时重试提交的逻辑。如果要将读取和写入操作与事务关联,则必须将会话传递给事务中的每个操作。具体代码如下:
2.3、事务错误处理
无论是哪种数据库系统(包括非关系数据库或关系数据库),应用程序都应采取相应措施来处理事务提交期间的错误,并包含事务的重试逻辑。
2.3.1、TransientTransactionError
无论retryWrites的值如何,事务中的单独写入操作均不可重试。如果操作遇到与标签相关的TransientTransactionError错误(例如主节点降级时),可以将事务作为一个整体进行重试。
回调API包含TransientTransactionError错误的重试逻辑。核心事务API不包含TransientTransactionError的重试逻辑。如果要处理TransientTransactionError,则应用程序应显式包含错误的重试逻辑。
2.3.2、UnknownTransactionCommitResult
提交操作是可以重试写入操作的。如果提交操作遇到错误,那么无论retryWrites的值如何,MongoDB驱动程序都会重试提交。如果提交操作遇到标记为UnknownTransactionCommitResult的错误,则可以重试提交。
回调API包含UnknownTransactionCommitResult的重试逻辑。核心事务API不包含UnknownTransactionCommitResult的重试逻辑,如果要处理UnknownTransactionCommitResult,则应用程序应显式包含错误的重试逻辑。
2.3.3、TransactionTooLargeForCache
从MongoDB 6.2版本开始,服务器在收到TransactionTooLargeForCache错误后不会重试事务。此错误意味着缓存过小,重试可能会失败。TransactionTooLargeForCacheThreshold阈值的默认值为0.75,当事务使用超过75%的缓存时,服务器会返回TransactionTooLargeForCache而不是重试事务。
在MongoDB的早期版本中,服务器会返回TemporarilyUnavailable或WriteConflict,而不是TransactionTooLargeForCache。
在下面的mongosh代码示例中,主要省略了重试逻辑和强大的错误处理功能。具体代码如下:
3、事务与操作
MongoDB数据库可以在跨多个操作、集合、数据库、文档和分片上使用分布式事务,具体内容如下。
3.1、事务操作基础
MongoDB数据库在事务中创建集合和索引,如果事务不是跨分片写入事务,则可以在分布式事务中执行创建集合的操作,并在先前同一事务中创建的新空集合上创建索引。对于MongoDB事务操作而言,主要包含以下几方面的内容:
- 可以在事务中创建集合和索引。
- 事务中使用的集合可以位于不同的数据库中。注意:设计人员无法在跨分片写事务中创建新集合。
- 不能写入固定大小集合。
- 从固定大小集合读取时,不能使用读关注snapshot(从MongoDB 5.0版本开始)。
- 不能在config、admin或local数据库中读取/写入集合。
- 不能写入system.*集合。
- 不能使用explain或类似命令返回受支持操作的查询计划。
- 对于在ACID事务外部创建的游标,无法在ACID事务内部调用getMore。
- 对于在事务中创建的游标,无法在事务外部调用getMore。
- 不能将killCursors指定为事务中的第一个操作。
3.2、在事务中创建集合和索引
在MongoDB事务中创建集合时,可以隐式创建一个集合。例如,对不存在的集合进行插入操作,或对不存在的集合使用upsert: true进行update/findAndModify操作。同时,可以使用create命令或其辅助程序db.createCollection()显式创建集合。
同时,创建集合和索引有如下几项限制。
- 无法在跨分片写事务中创建新集合。例如,如果在一个分片中写入一个现有集合,并在另一个分片中隐式创建一个集合,MongoDB将无法在同一事务中执行这两个操作。
- 当以分片集合为目标时,无法在事务中使用$graphLookup阶段。
- 如果要在事务中显式创建集合或索引,则事务读关注级别必须为"local"。
3.3、计数 限制性与去重操作
如果要在MongoDB事务中执行计数操作,可以使用$count聚合阶段或$group(带有$sum表达式)聚合阶段。MongoDB驱动程序提供集合级API方法countDocuments(filter, options)作为辅助方法,该方法使用$group和$sum表达式来执行计数。mongosh工具提供db.collection.countDocuments()辅助方法,该方法使用$group和$sum表达式进行计数。
如果要在事务中执行不同的操作,对于未分片的集合,可以使用db.collection.distinct()方法、distinct命令以及带有$group阶段的聚合管道。对于分片集合,不能使用db.collection.distinct()方法或distinct命令。
如果要查找分片集合的不同值,可以改用带有$group阶段的aggregation pipeline。例如,不使用db.coll.distinct(“x”),而是使用如下方法:
不使用db.coll.distinct(“x”, { status: “A” }),而是使用如下方法:
管道返回一个指向文档的游标:
{"distinctValues":[2,3,1]}以上迭代游标用于访问结果文档。
MongoDB事务中有几项限制性操作,具体内容如下:
- 在跨分片写事务中创建新集合。例如,如果在一个分片中写入一个现有集合,并在另一个分片中隐式创建一个集合,那么MongoDB将无法在同一事务中执行这两项操作。
- 使用local以外的读关注级别时,显式创建集合(例如db.createCollection()方法)和索引(例如db.collection.createIndexes()和db.collection.createIndex()方法)。
- listCollections和listIndexes命令及其辅助方法。
- 其他非CRUD和非信息性操作(例如createUser、getParameter和count)及其辅助程序。
4、读取偏好与读写关注
4.1、事务和读取偏好
MongoDB数据库事务中的操作使用事务级读取偏好。通过使用驱动程序,设计人员可以在事务启动时设置事务级读取偏好,具体内容如下:
- 如果未设置事务级别的读取偏好,则事务将使用会话级别的读取偏好。
- 如果未设置事务级别和会话级别的读取偏好,则事务将使用客户端级别的读取偏好。默认情况下,客户端级别的读取偏好为primary。
- 包含读取操作的分布式事务必须使用读取偏好primary。给定事务中的所有操作都必须路由到同一节点。
4.2、事务和读关注
MongoDB数据库事务中的操作使用事务级读关注,也就是在集合和数据库级别设置的任何读关注在事务中都会被忽略。设计人员可以在事务启动时设置事务级别的读关注,具体内容如下:
- 如果未设置事务级别的读关注,则事务级别的读关注默认为会话级别的读关注。
- 如果未设置事务级的读关注和会话级的读关注,则事务级的读关注默认为客户端级的读关注。默认情况下,对于主节点上的读取,客户端级的读关注是local。
关于MongoDB数据库事务支持的读关注级别,具体内容介绍如下。
- local
- 读关注local返回节点中可用的最新数据,但可以回滚。
- 在副本集上,即使事务使用读关注local,也可能会观察到更强的读隔离性,其中该操作从事务打开时的快照中读取。
- 对于分片集群上的事务,读关注local无法保证数据来自跨分片的同一快照视图。
- 可以在事务中创建集合和索引。如果显式创建集合或索引,则事务必须使用读关注local。如果隐式创建集合,则可以使用任何可用于事务的读关注。
- majority
- 如果事务以写关注majority提交,则读关注majority返回已被多数副本集节点确认且无法回滚的数据。否则,读关注majority不保证读取操作会读取多数副本集中所提交的数据。
- 对于分片集群上的事务,读关注majority无法保证数据来自跨分片的同一快照视图。
- snapshot
- 如果事务使用写关注majority提交,则读关注snapshot会从多数已提交数据的快照中返回数据。
- 如果事务不使用写关注majority提交,则snapshot读关注不保证读操作会使用大多数已提交数据的快照。
- 对于分片集群上的事务,数据的snapshot视图会在各分片之间同步。
4.3、事务和写关注
MongoDB数据库事务使用事务级的写关注来提交写入操作。事务内的写入操作必须在没有明确写关注规范的情况下执行,并且必须使用默认的写关注。在提交时,使用事务级的写关注来提交写入。
设计人员可以在事务启动时设置事务级的写关注,具体内容如下:
如果未设置事务级的写关注,则事务级的写关注默认为提交的会话级的写关注。
如果未设置事务级的写关注和会话级的写关注,则事务级的写关注默认为客户端级的写关注,具体定义如下。
w: "majority":在MongoDB 5.0及更高版本中,包含仲裁节点的部署与之前版本有所不同。w: 1。
关于事务支持所有写关注的w值,具体定义如下:
- w: 1
- 写关注w: 1会在提交应用于主节点后返回确认信息。使用w: 1提交时,如果发生故障转移,则可以回滚事务。
- 使用w: 1写入关注提交时,事务级majority读关注无法保证事务中的读操作会读取大多数已提交数据。
- 使用w: 1写关注提交时,事务级snapshot读关注无法保证事务中的读操作会使用大多数已提交数据的快照。
- w: “majority”
- 在将提交应用于大多数投票节点后,写关注w:"majority"会返回确认消息。
- 使用w: "majority"写关注提交时,事务级"majority"读关注可以保证操作已读取大多数已提交数据。对于分片集群上的事务,大多数已提交数据的视图不会在各分片之间同步。
- 使用w: "majority"写关注提交时,事务级"snapshot"读关注可以保证操作已从大多数已提交数据的同步快照中读取。