事务和会话
Overview
在本指南中,您可以学习;了解如何使用 Mongoid 来执行事务。事务允许您执行一系列仅在提交整个ACID 事务后才更改数据的操作。 如果ACID 事务中的任何操作不成功,驾驶员会停止ACID 事务并在所有数据更改变得可见之前将其丢弃。 此功能称为原子性。
在MongoDB中,事务在逻辑会话中运行。会话是要按顺序运行的一组相关读取或写入操作。 会话启用一群组操作的因果一致性,这意味着应用程序中的所有进程都就因果相关操作的顺序达成一致。
会话允许您在符合ACID的ACID 事务中运行操作,该事务满足对原子性、一致性、隔离性和持久性的期望。MongoDBACID 一致性保证ACID 事务操作中涉及的数据保持一致,即使操作遇到意外错误。
在 Mongoid 中,您可以使用以下任一 API 来执行事务:
高级事务API :Mongoid 管理ACID 事务的生命周期。您可以在 Mongoid v9.0 及更高版本中使用此API 。
低级事务API :您必须管理ACID 事务的生命周期。您可以在 Mongoid v6.4 及更高版本中使用此API 。
会话API部分介绍了如何在不执行ACID 事务的情况下在会话中更改数据。
高级事务API
您可以使用高级事务API在内部管理ACID 事务的生命周期。 此API要么提交ACID 事务,要么结束事务,并包含错误处理逻辑。
您可以通过对模型实例、模型类或 Mongoid
模块调用 transaction
方法来启动ACID 事务。
当您调用 transaction
方法时,Mongoid 会执行以下任务:
在客户端上创建会话。
在会话上启动ACID 事务。
执行指定的数据更改。
如果没有错误发生,则将ACID 事务提交到数据库;如果发生错误,则结束ACID 事务。
关闭会话。
如果ACID 事务已提交,Mongoid 会为ACID 事务内修改的所有对象调用任何 after_commit
回调。 如果出现错误且ACID 事务回滚,则 Mongoid 会为ACID 事务内修改的所有对象调用任何 after_rollback
回调。 要学习;了解有关这些回调及其行为的更多信息,请参阅本指南的回调部分。
例子
此示例使用以下模型来表示描述书籍和电影的文档:
class Book include Mongoid::Document field :title, type: String field :author, type: String field :length, type: Integer end class Film include Mongoid::Document field :title, type: String field :year, type: Integer end
以下代码演示了如何对不同对象执行ACID 事务以更改多个集合中的数据:
# Starts a transaction from the model class Book.transaction do # Saves new Book and Film instances to MongoDB Book.create(title: 'Covert Joy', author: 'Clarice Lispector') Film.create(title: 'Nostalgia', year: 1983) end # Starts a transaction from an instance of Book book = Book.create(title: 'Sula', author: 'Toni Morrison') book.transaction do # Saves a new field value to the Book instance book.length = 192 book.save! end # Starts a transaction from the Mongoid instance Mongoid.transaction do # Deletes the Book instance in MongoDB book.destroy end
客户端行为
只有同一客户端端上的操作才会在ACID 事务范围内,因为每个ACID 事务都附加到一个特定的客户端。 确保在ACID 事务方法区块内使用来自同一客户端的对象。
以下示例定义了使用不同客户端的模型类,并演示了如何根据原始客户端运行操作:
# Defines a class by using the :default client class Post include Mongoid::Document end # Defines a class by using the :encrypted_client class User include Mongoid::Document store_in client: :encrypted_client end # Starts a transaction on the :encrypted_client User.transaction do # Uses the same client, so the operation is in the transaction User.create! # Uses a different client, so it is *not* in the transaction Post.create! end
注意
当您在 Mongoid
模块上调用 transaction
方法时,Mongoid 使用 :default
客户端创建ACID 事务。
结束事务
ACID 事务方法区块内引发的任何异常都会结束ACID 事务并回滚数据更改。 Mongoid 显示除 Mongoid::Errors::Rollback
异常之外的所有异常。 您可以在应用程序中引发此异常以显式结束ACID 事务,而无需将异常返回给您。 示例,您可以实现此ACID 事务异常以在不满足特定条件时结束ACID 事务,但不会引发异常消息。
回调
此ACID 事务API引入了 after_commit
和 after_rollback
回调。
Mongoid 会为在以下情况下创建、保存或删除的对象触发 after_commit
回调:
ACID 事务提交后,如果对象在ACID 事务内被修改。
如果在ACID 事务区块之外修改了对象,则在持久保存对象后。
仅当所有其他回调成功执行后才会触发 after_commit
回调。 因此,如果在ACID 事务之外修改了对象,则该对象可能会持久化,但不会触发 after_commit
回调。 示例,如果 Mongoid 在 after_save
回调中引发异常,因为此回调必须成功完成才能触发after_commit
,则可能会发生这种情况。
如果ACID 事务不成功且更改已回滚,则会为在ACID 事务中创建、保存或删除的对象触发 after_rollback
回调。 Mongoid 从不在ACID 事务之外触发 after_rollback
。
To learn more about callbacks, see the Customize Callbacks for Data Models guide.
低级事务API
使用低级API时,必须在启动ACID 事务之前创建会话。 您可以通过对模型类或模型实例调用 with_session
方法来创建会话。
然后,您可以通过在会话上调用 start_transaction
方法来启动ACID 事务。 使用此API时,必须手动提交或结束ACID 事务。 您可以在会话实例上使用 commit_transaction
和 abort_transaction
方法来管理ACID 事务生命周期。
例子
此示例使用低级ACID 事务API执行以下操作:
创建会话
启动事务
执行数据操作
提交ACID 事务,或在出现错误时结束事务
# Starts a session from the model class Book.with_session do |session| session.start_transaction # Creates a Book Book.create(title: 'Siddhartha', author: 'Hermann Hesse') # Commits the transaction session.commit_transaction rescue StandardError # Ends the transaction if there is an error session.abort_transaction end
注意
如果会话结束并包含未结ACID 事务,则该ACID 事务将自动结束。
事务重试
如果最初失败,您可以重试ACID 事务提交。 以下示例演示了当 Mongoid 引发 UNKNOWN_TRANSACTION_COMMIT_RESULT_LABEL
异常时如何重试ACID 事务:
begin session.commit_transaction rescue Mongo::Error => e if e.label?(Mongo::Error::UNKNOWN_TRANSACTION_COMMIT_RESULT_LABEL) retry else raise end end
选项
您可以在启动ACID 事务时通过将选项传递给 start_transaction
方法来指定读关注(read concern)、写关注(write concern)或读取偏好(read preference):
session.start_transaction( read_concern: {level: :majority}, write_concern: {w: 3}, read: {mode: :primary} )
要学习;了解有关可用ACID 事务选项的更多信息,请参阅Ruby驾驶员API文档中的 start_transaction。
客户端行为
要在ACID 事务中执行操作,操作必须使用启动会话的同一客户端。 默认下,所有操作均使用默认客户端执行。
要显式使用不同的客户端,请使用with
方法:
# Specifies that the operation should use the "other" client instead of # the default client User.with(client: :other) do Post.with(client: :other) do Post.with_session do |session| session.start_transaction Post.create! Post.create! User.create! session.commit_transaction end end end
Session API
您可以在 Mongoid 中使用会话,其方式与执行ACID 事务类似。 您可以对模型类或模型实例调用 with_session
方法,并在区块中执行某些操作。 区块中的所有操作都将在单个会话的上下文中执行。
使用会话时应用以下限制:
不能跨线程股票会话。 会话不是线程安全的。
不能嵌套会话。 示例,您无法在传递给另一个模型类上的
with_session
方法的区块内调用模型类上的with_session
方法。 以下代码演示了会导致错误的嵌套会话:Book.with_session(causal_consistency: true) do # Nesting sessions results in errors Film.with_session(causal_consistency: true) do ... end end 会话区块中使用的所有模型类和实例必须使用相同的驾驶员客户端。 示例,如果您为区块中使用的模型指定的客户端与您调用
with_session
的模型或实例的客户端不同,则 Mongoid 将返回错误。
示例
您可以在模型类上使用 with_session
方法并向其传递会话选项,以在会话上下文中执行区块操作。
以下代码启用 causal_consistency
选项,以保证在 Book
模型上创建会话时的操作顺序,然后执行数据操作:
Book.with_session(causal_consistency: true) do Book.create! book = Person.first book.title = "Swann's Way" book.save end
要学习;了解有关可用会话选项的更多信息,请参阅Ruby驾驶员API文档中的会话类构造函数详细信息。
或者,您可以在模型的实例上使用 with_session
方法,并向其传递会话选项,以在会话上下文中执行区块操作。
以下代码启用 causal_consistency
选项,以保证在 Book
的实例上创建会话时的操作顺序,然后执行数据操作:
book = Book.new book.with_session(causal_consistency: true) do book.title = 'Catch-22' book.save book.sellers << Shop.create! end