使用数据库事务防止竞争条件(Laravel)

时间:2017-03-04 07:52:47

标签: php sql database laravel transactions

如何防止这种竞争状况发生?我知道Laravel中的事务阻止了更新,但是如何防止使用陈旧数据?是否有办法在另一个事务进行时锁定数据库而不读取? (即让第二个请求等待第一个请求完成?)

假设数据库中主键id = 7的用户名字段为空。

请求1进来并执行此操作:

public function raceCondition1() {

    DB::beginTransaction();

    //Get the model with primary key 7
    $user = User::findorfail(7);

    sleep(6);

    $user->username = 'MyUsername';

    $user->save();

    DB::commit();
}

两秒秒后,我运行了Request 2,它只是将某些内容连接到用户名列并保存:

public function raceCondition2() {

    DB::beginTransaction();

    $user = User::findorfail(7);

    sleep(6);

    $user->username = 'USER_' . $user->username;

    $user->save();

    DB::commit();
}

在这种情况下,数据库中的结果是:USER _

在第一个请求可以保存之前从数据库读取的第二个请求,并使用过时的NULL值。是否有办法在另一个事务进行时锁定数据库而不读取? (即让第二个请求等待第一个请求完成?)

4 个答案:

答案 0 :(得分:4)

Laravel支持“悲观锁定”。有关详细信息,请参阅Laravel documentation on pessimistic locking

答案 1 :(得分:0)

当您执行查询时,会使用事务,并且如果在执行期间可能出错,则希望有一种机制来反转生成的修改。

您正在寻找的是内部锁定方法,其中对数据库的每个请求都放在队列中,并且仅在处理上一个请求时处理。

我不知道这个功能是否来自laravel ORM,但它可以通过经典的SQL查询轻松实现。

查看此链接,了解整个机制的工作原理,我相信您所寻找的是行级锁定。

Locking Methods

答案 2 :(得分:0)

要解决应用程序的竞争条件问题需要高性能,乐观锁定比悲观锁定要好,因为悲观锁定可能会产生死锁。

实际上,乐观锁定不是数据库功能,它只是最佳实践。

有关更多详细信息,您可以检查以下很好的答案:Optimistic locking in MySQL

答案 3 :(得分:0)

我在做什么,因为我遇到很多错误

  

production.ERROR:SQLSTATE [23000]:违反完整性约束:1062重复的条目

这是基于使用队列的多线程表插入...我认为可以先在Laravel中执行此操作,然后尝试updateOrCreate,然后我要说,考虑到所有情况,这是一个巨大的疏忽是多用途多线程多人...等等等等...这对我来说很简单。至少到目前为止,它似乎仍然有效

public function firstOrCreate(array $attributes, array $values = []) {
     if (! is_null($instance = $this->where($attributes)->first())) {
          return $instance;
     }
     try {
         return tap($this->newModelInstance($attributes + $values), function ($instance) {$instance->save();});
    } catch (Exception $e) {
        return $this->where($attributes)->first();
    }
}

现在,如果竞争条件是那种有多个冲突的种族,这仍然可能失败,但是可以通过使函数循环冗余或仅使其重复几次来进行某种程度的套期保值...看起来很cr脚,但我再也没有发生碰撞...

public function firstOrCreate(array $attributes, array $values = []) {
    if (! is_null($instance = $this->where($attributes)->first())) {
        return $instance;
    }
    try {
        return tap($this->newModelInstance($attributes + $values), function ($instance) {$instance->save();});
    } catch (Exception $e) {
            if (! is_null($instance = $this->where($attributes)->first())) {
                return $instance;
            }
            try {
                return tap($this->newModelInstance($attributes + $values), function ($instance) {$instance->save();});
           } catch (Exception $e) {
                return $this->where($attributes)->first();
           }
       }
    }