Skip to content

Instantly share code, notes, and snippets.

@troatie
Last active September 12, 2023 13:51
Show Gist options
  • Star 45 You must be signed in to star a gist
  • Fork 9 You must be signed in to fork a gist
  • Save troatie/def0fba42fcfb70f873b7f033fbe255f to your computer and use it in GitHub Desktop.
Save troatie/def0fba42fcfb70f873b7f033fbe255f to your computer and use it in GitHub Desktop.
Guard against race conditions in Laravel's firstOrCreate and updateOrCreate
trait CreatesWithLock
{
public static function updateOrCreate(array $attributes, array $values = [])
{
return static::advisoryLock(function () use ($attributes, $values) {
// emulate the code found in Illuminate\Database\Eloquent\Builder
return (new static)->newQuery()->updateOrCreate($attributes, $values);
});
}
public static function firstOrCreate(array $attributes, array $values = [])
{
return static::advisoryLock(function () use ($attributes, $values) {
return (new static)->newQuery()->firstOrCreate($attributes, $values);
});
}
/**
* In my project, this advisoryLock method actually lives as a function on the global namespace (similar to Laravel Helpers).
* In that case the $lockName, and default lock duration are pased in as arguments.
*/
private static function advisoryLock(callable $callback)
{
// Lock name based on Model.
$lockName = substr(static::class . ' *OrCreate lock', -64);
// Lock for at most 10 seconds. This is the MySQL >5.7.5 implementation.
// Older MySQL versions have some weird behavior with GET_LOCK().
// Other databases have a different implementation.
\DB::statement("SELECT GET_LOCK('" . $lockName . "', 10)");
$output = $callback();
\DB::statement("SELECT RELEASE_LOCK('" . $lockName . "')");
return $output;
}
}
@mpyw
Copy link

mpyw commented Sep 2, 2023

This has been completely resolved since Laravel 10.22.0, Please read it through translation: [Laravel] createOrFirst の登場から激変した firstOrCreate, updateOrCreate に迫る!

If retry processing under unique key constraints is not sufficient: mpyw/laravel-database-advisory-lock: Advisory Locking Features for Postgres/MySQL/MariaDB on Laravel

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment