Docs 菜单

事务和会话

在本指南中,您可以学习;了解如何使用 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在内部管理ACID 事务的生命周期。 此API要么提交ACID 事务,要么结束事务,并包含错误处理逻辑。

您可以通过对模型实例、模型类或 Mongoid 模块调用 transaction 方法来启动ACID 事务。

当您调用 transaction 方法时,Mongoid 会执行以下任务:

  1. 在客户端上创建会话。

  2. 在会话上启动ACID 事务。

  3. 执行指定的数据更改。

  4. 如果没有错误发生,则将ACID 事务提交到数据库;如果发生错误,则结束ACID 事务。

  5. 关闭会话。

如果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_commitafter_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时,必须在启动ACID 事务之前创建会话。 您可以通过对模型类或模型实例调用 with_session 方法来创建会话。

然后,您可以通过在会话上调用 start_transaction 方法来启动ACID 事务。 使用此API时,必须手动提交或结束ACID 事务。 您可以在会话实例上使用 commit_transactionabort_transaction 方法来管理ACID 事务生命周期。

此示例使用低级ACID 事务API执行以下操作:

  1. 创建会话

  2. 启动事务

  3. 执行数据操作

  4. 提交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

您可以在 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

要学习;了解有关事务的更多信息,请参阅服务器手册中的事务。

要学习;了解有关执行增删改查操作的更多信息,请参阅执行数据操作指南。