事务

开启事务

开启事务后,事务之间的所有操作都是同一个连接,注意不能使用并发操作。

如果你想要开始一个事务,并且对回滚和提交能够完全控制,那么你可以使用 DB 的 beginTransaction 方法:

  1. DB::beginTransaction();

或者

  1. DB::connection()->beginTransaction();

一旦开启了事务,当前连接会绑定到当前的协程环境中,保证提交回滚查询都是同一个连接保证数据的安全性,只有提交或者回滚完毕才会解除绑定。

事务回滚

如果操作失败需要回滚 使用下面两种方式都可以

  1. DB::rollBack();

或者

  1. DB::connection()->rollBack();

事务提交

如果操作执行成功需要 使用下面两种方式都可以

  1. DB::commit();

或者

  1. DB::connection()->commit();

常见问题

事务是否支持嵌套

MySQL 官方文档说 如果事务发生嵌套,会隐式提交上一个事务,然后开启一个新的事务。

框架的答案是可以做事务嵌套,因为部分数据库支持 支持 savepoints 能力。
在MySQL中, 保存点savepoints属于事务控制处理部分。利用savepoints可以回滚指定部分事务,从而使事务处理更加灵活和精细。如果你进行了事务嵌套嵌套的事务会保存在savepoints里面,可以做嵌套事务的精细控制。

  1. DB::beginTransaction();
  2. $user = User::find($id);
  3. $user->update(['name' => $id]);
  4. DB::beginTransaction();
  5. User::find($id)->update(['name'=>'sakuraovq']);
  6. DB::rollBack();
  7. DB::commit();

嵌套里面的的事务进行了回滚,不会影响外层改变的数据只回滚name=sakuraovq的修改,这段代码执行下来 name = $id

如果事务没有提交怎么办

  1. DB::connection()->beginTransaction();
  2. $user = User::find($id);
  3. \sgo(function ()use($id) {
  4. DB::connection()->beginTransaction();
  5. User::find($id);
  6. });

类似这样的代码如果我们忘记 提交事务/回滚。

Swoft 在SwoftEvent::COROUTINE_DEFER事件中会检查是否还处于事务状态,如果是会自动 rollback到最初开启事务的状态。连接会归还到连接池中,不会造成资源泄露。

错误示范

  1. DB::beginTransaction();
  2. $user = User::find($id);
  3. \sgo(function () use ($id) {
  4. $user1 = User::find($id);
  5. });
  6. $user->update(['name' => 'sakuraovq'.mt_rand(110,10000)]);
  7. DB::commit();

类似这样的代码虽然执行没有问题,这种写法是错误的,会造成数据的错乱。请不要在事务中嵌套协程,在执行 db 操作, 上文讲到事务是绑定到当前协程的 切换了协程也就是另一个新的连接。

  1. DB::connection()->beginTransaction();
  2. $user = User::find($id);
  3. $user->update(['name' => 2]);
  4. \sgo(function () use ($user) {
  5. $user->update(['name' => 1]);
  6. });
  7. DB::rollBack();

这样也是错的,这段代码执行下来你会发现,子协程的修改操作回滚不受控制。因为它们使用了不同的连接,并非绑定在一起。