事务
概述
在本指南中,您可以学习如何使用Laravel MongoDB在MongoDB中执行事务。事务允许您运行一系列写入操作,只有在事务提交后才会更新数据。
如果事务失败,管理MongoDB操作的MongoDB PHP库会确保MongoDB在事务更改变得可见之前丢弃事务中做出的所有更改。事务的这一属性,即确保事务中的所有更改要么全部应用,要么全部丢弃,被称为原子性。
MongoDB以原子方式对单个文档执行写入操作。如果您需要在多个文档的写入操作中实现原子性或跨多个文档的操作保持数据一致性,请将它们在多文档事务中运行。
多文档事务是ACID兼容的,因为MongoDB保证即使驱动程序遇到意外错误,事务操作中的数据也保持一致。
有关MongoDB中事务的更多信息,请参阅事务(服务器手册中的事务)。
本指南包含以下部分
要求和限制
要在MongoDB中执行事务,您必须使用以下MongoDB版本和拓扑
MongoDB版本4.0或更高版本
副本集部署或分片集群
MongoDB服务器和Laravel集成有以下限制
在MongoDB 4.2版本及以下版本中,事务内的写操作必须在现有集合上执行。在MongoDB 4.4版本及更高版本中,当你在一个事务中执行写操作时,服务器会自动根据需要创建集合。有关此限制的更多信息,请参阅服务器手册中的在事务中创建集合和索引。
MongoDB不支持嵌套事务。如果您在另一个事务中尝试启动事务,扩展会引发一个
RuntimeException
。有关此限制的更多信息,请参阅服务器手册中的事务和会话。Laravel MongoDB不支持数据库测试特性
Illuminate\Foundation\Testing\DatabaseTransactions
和Illuminate\Foundation\Testing\RefreshDatabase
。作为替代方案,您可以使用具有Illuminate\Foundation\Testing\DatabaseMigrations
特性的迁移来在每个测试后重置数据库。
在回调中运行事务
本节展示了如何在回调中运行事务。
使用此方法运行事务时,回调方法中的所有代码作为一个单一的事务运行。
在以下示例中,事务包括将资金从由Account
模型表示的银行账户转移到另一个账户的写操作
DB::transaction(function () { $transferAmount = 200; $sender = Account::where('number', 223344)->first(); $sender->balance -= $transferAmount; $sender->save(); $receiver = Account::where('number', 776655)->first(); $receiver->balance += $transferAmount; $receiver->save(); });
您可以选择将尝试重试失败事务的最大次数作为第二个参数传递,如下面的代码示例所示
DB::transaction(function() { // transaction code }, attempts: 5, );
开始和提交事务
本节展示了如何开始和提交事务。
要使用此方法开始和提交事务,请调用DB::beginTransaction()
方法来开始事务。然后,调用DB::commit()
方法来结束事务,该事务将应用事务内执行的所有更新。
在以下示例中,将第一个账户的余额转移到第二个账户,之后删除第一个账户。
DB::beginTransaction(); $oldAccount = Account::where('number', 223344)->first(); $newAccount = Account::where('number', 776655)->first(); $newAccount->balance += $oldAccount->balance; $newAccount->save(); $oldAccount->delete(); DB::commit();
回滚事务
本节展示了如何回滚事务。回滚会撤销事务中执行的所有写操作。这意味着数据会回滚到事务开始前的状态。
要执行回滚,在任何提交事务之前调用 DB::rollback()
函数。
在以下示例中,事务包括将资金从一个表示为 Account
模型的账户转移到多个其他账户的写操作。如果发送账户资金不足,则事务将回滚,并且所有模型都不会更新。
DB::beginTransaction(); $sender = Account::where('number', 223344)->first(); $receiverA = Account::where('number', 776655)->first(); $receiverB = Account::where('number', 990011)->first(); $amountA = 100; $amountB = 200; $sender->balance -= $amountA; $receiverA->balance += $amountA; $sender->balance -= $amountB; $receiverB->balance += $amountB; if ($sender->balance < 0) { // insufficient balance, roll back the transaction DB::rollback(); } else { DB::commit(); }