如何为 Laravel 的 `firstOrCreate` 锁定数据库?

问题描述 投票:0回答:3

我们当前在执行以下代码时遇到

Duplicate entry
QueryException:

Slug::firstOrCreate([
  Slug::ENTITY_TYPE => $this->getEntityType(),
  Slug::SLUG        => $slug
], [
  Slug::ENTITY_ID   => $this->getKey()
]);

由于 Laravel 的

firstOrCreate
方法在插入之前首先检查具有属性的条目是否存在,因此永远不会发生此异常。然而,我们的应用程序每天有数百万个访问者和数百万个操作,因此还使用主数据库连接与两个从数据库进行读取。因此,可能会出现一些竞争条件。

我们目前尝试分离查询并强制主连接进行读取:

 $slugModel = Slug::onWriteConnection()->where([
     Slug::SLUG        => $slug,
     Slug::ENTITY_TYPE => $this->getEntityType()
 ])->first();

 if ($slugModel && $slugModel->entity_id !== $this->getKey()) {
    $class = get_class($this);
    throw new \RuntimeException("The slug [{$slug}] already exists for a model of type [{$class}].");
}

if (!$slugModel) {
   return $this->slugs()->create([
         Slug::SLUG        => $slug,
         Slug::ENTITY_TYPE => $this->getEntityType()
   ]);
}

但是有时还是会出现异常。

我们的下一个方法是在读取检查之前锁定表,并在写入之后释放锁定,以防止在读取和写入之间的其他数据库操作中使用相同的 slug 进行任何插入。有谁知道如何解决这个问题?我真的不明白 Laravel 的悲观锁定 如何帮助解决这个问题。我们使用 MySql 作为我们的数据库。

php mysql laravel eloquent locking
3个回答
2
投票

我不建议锁定桌子,特别是如果你有数百万观众。

大多数竞争条件可以通过锁来修复,但这不能通过锁来修复,因为你无法锁定不存在的行(有类似间隙锁定的东西,但这在这里没有帮助。)。

Laravel 本身不处理竞争条件。如果您调用firstOrCreate,它会执行两个查询:

  1. 选择 slug=X 且entity_type=Y 的项目
  2. 如果不存在,则创建它

现在因为我们有两个查询,竞争条件是可能的,这意味着两个用户并行到达步骤 1,然后都尝试在步骤 2 中创建条目,并且您的系统将崩溃。

由于您已经遇到了

Duplicate Key
错误,这意味着您已经对标识行的两列上的元组施加了唯一的约束,这很好。

您现在可以做的是捕获重复键错误,如下所示:

try{
    $slug = Slug::firstOrCreate([
  Slug::ENTITY_TYPE => $this->getEntityType(),
  Slug::SLUG        => $slug
], [
  Slug::ENTITY_ID   => $this->getKey()
]);
}
catch (Illuminate\Database\QueryException $e){
    $errorCode = $e->errorInfo[1];
    if($errorCode == 1062){
        $slug = Slug::where('slug','=', $slug)->where('entity_type','=',  $this->getEntityType())->first();
    }
}

0
投票

解决此问题的一个解决方案是使用 Laravel 队列并确保它一次运行一项作业,这样您就永远不会同时有 2 个相同的查询。

如果你想在同一个请求中返回结果,这肯定行不通。


0
投票

这个问题从Laravel 10.22.0开始已经彻底解决,请通过翻译阅读:[Laravel] createOrFirstの登场から激変したfirstOrCreate,updateOrCreateに迫る!

如果在唯一键约束下重试处理还不够:mpyw/laravel-database-advisory-lock:Laravel 上 Postgres/MySQL/MariaDB 的咨询锁定功能

© www.soinside.com 2019 - 2024. All rights reserved.