-
Notifications
You must be signed in to change notification settings - Fork 11.1k
New issue
Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.
By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.
Already on GitHub? Sign in to your account
Duplicate entries on updateOrCreate #19372
Comments
Whats your database structure, share migration for the table. |
Can you share your database structure or migration file for the table? |
Schema::create('devices', function (Blueprint $table) {
$table->increments('id');
$table->integer('user_id')->unsigned()->nullable();
$table->string('uuid')->unique();
$table->timestamps();
}); |
Since it happens on random occasions I believe it might be a race condition with the record is being created via another request in between the check and insert inside updateOrCreate(). |
Well. I think this is a Laravel mass assignment Issue.
I put the solution code here. |
Hi @Reaper45! |
Feel free to open a PR if you have a better idea, meanwhile I'm closing this since it's not actually a bug. |
I am having the exact same problem. |
i think you could use lock to prevent simultaneous calls.
or something like that i've not tested it. |
Yes, I could. |
this is still problem. |
I solved this problem by overriding Illuminate\Database\Eloquent\Model and creating a custom firstOrCreate method that returns the model if there is a duplicate error exception. This should work for mySQL connections. class SystemModel extends Illuminate\Database\Eloquent\Model
{
/**
* Check if Illuminate\Database\QueryException is Duplicate Entry Exception.
*
* @param array $attributes
* @return \Illuminate\Database\Eloquent\Model
*/
protected function isDuplicateEntryException(Illuminate\Database\QueryException $e){
$errorCode = $e->errorInfo[1];
if ($errorCode === 1062) { // Duplicate Entry error code
return true;
}
return false;
}
/**
* Get the first record matching the attributes or create it.
*
* @param array $attributes
* @param array $values
* @return \Illuminate\Database\Eloquent\Model
*/
public static function firstOrCreate(array $attributes, array $values = [])
{
try {
$static = (new static);
return $static->create($attributes + $values);
} catch (Illuminate\Database\QueryException $e){
if($static->isDuplicateEntryException($e)) {
return $static->where($attributes)->first();
}
}
}
} |
Please reopen this ticket. I'd agree with other commenters that this is an actual bug since it is surprising behavior for any moderately busy service where collisions are possible. While the implementation does not concern me the syntax implies it is somehow atomic. This could be achieved in a variety of ways:
In my opinion All that said, I can see why the |
@themsaid this really is a bug. Race conditions are a big problem with updateOrCreate. We are fighting this at present, upsert is a safe method with mysql |
Manage race conditions manually when using updateOrCreate with an ORM in 21st century?? C'mon! |
Guys, please open a PR if you have a different approach that works on all supported database engines :) PRs are welcomed. |
This is still a problem, albeit occurs very rarely. |
Still, I am getting the same issue. (3/3) QueryException |
I too am having this issue. For now, the only solution seems to be the old long form method of checking if there's an instance of the model, if not then create a new one, fill it, save, etc. Super inconvenient. I'm not even sure how this is happening on my end. I don't think the calls are happening close enough together to cause the duplicates. Anyway, just hoping there's a better solution soon. Thanks. |
See this issue: laravel/framework#19372
@didioh 's approach works for me, as does adding a trait to the affected Model to override the updateOrCreate and firstOrCreate methods, as described here. I agree with others that this should be default behaviour. |
I have a problem that may be the cause of your problem. My table has an id column, language and description, I'm running the method for validating the id and language. To insert everything occurs perfectly. ID_|_ LANGUAGE_ |_ DESCRIPTION The problem occurs when it is updated, when sending to update
it will be like this ID_ |_ LANGUAGE _ |_ DESCRIPTION Laravel 5.6 |
I had the same issue. I found a situation in which $order->order_products()->createMany($added_products); So (because there isn't foreach($added_products as $added_product) {
$order->order_products()->updateOrCreate($added_product);
} But that didn't work. Then I figured out that I hadn't specified the My solution, with composite primary key supportforeach($added_products as $added_product) {
// $order->order_products()->updateOrCreate($added_product);
\Helper::updateOrCreate($order->order_products(), $added_product);
} And in the helper class: public static function updateOrCreate($object, $attributes) {
$primaryKey = [];
$modelPrimaryKey = (array)$object->getRelated()->getKeyName();
$attributes_withParentKey = $attributes;
$attributes_withParentKey[$object->getForeignKeyName()] = $object->getParentKey();
$values = $attributes; // without primary key
foreach($modelPrimaryKey as $field) {
$primaryKey[$field] = $attributes_withParentKey[$field];
unset($values[$field]);
}
DB::transaction(function() use(&$object, $primaryKey, $values, $attributes) {
// if record exists
if (DB::table($object->getRelated()->getTable())->where($primaryKey)->first()) {
// can't do $object->update($attributes) because one part of the PK would be in $attributes
DB::table($object->getRelated()->getTable())->where($primaryKey)->update($values);
} else {
$object->create($attributes);
}
}, 5);
} Also, public function updateOrInsert(array $attributes, array $values = [])
{
if (! $this->where($attributes)->exists()) {
return $this->insert(array_merge($attributes, $values));
}
return (bool) $this->take(1)->update($values);
} Shouldn't the method use DB::transaction? What if the record get deleted between |
This might be of help: Maybe we should make a pullrequest for this trait? https://gist.github.com/troatie/def0fba42fcfb70f873b7f033fbe255f |
Just remove your foreign key from the $fillable array property and it will work. Foreign key should not be mass assigned. It worked for me. |
I've got (sometimes) the same issue:
only the ID is guarded but sometimes it adds duplicates. |
It's a race condition you should leverage the lock logic. |
I've got the same issue (sometimes): |
I've also got the same rarely random issue when use updateOrCreate |
I've created an UPSERT package for all databases: https://github.com/staudenmeir/laravel-upsert |
Yeah, this is definitely still a problem. Whats the point in an updateORcreate method if it does the exact opposite |
Methods |
Same problem |
I must admit I'm kinda stunned this hasn't been fixed natively in the Eloquent codebase. I've reverted to using a trait on my model classes that that wraps a "create advisory lock" mysql call around updateOrCreate() and firstOrCreate() - but it's a hack and will only work for MySQL. |
At the very least the docs should be explicit that these methods are not implemented using native database SQL queries, and are therefore not "transaction safe". |
Is it possible to have different fixes for different database engines with the same outcome? We should at least update the docs with a warning about using firstOrCreate/updateOrCreate methods: |
Let’s tweet to TO
On Wed, Jul 17, 2019 at 11:01 AM Peter Duda ***@***.***> wrote:
Guys, please open a PR if you have a different approach that works on all
supported database engines :) PRs are welcomed.
Is it possible to have different fixes for different database engines with
the same outcome?
We should at least update the docs with a warning about using
firstOrCreate/updateOrCreate methods:
https://laravel.com/docs/5.8/eloquent#inserting-and-updating-models
—
You are receiving this because you commented.
Reply to this email directly, view it on GitHub
<#19372?email_source=notifications&email_token=AANGRWRAIAD6E7D5E5V2LODP73UYPA5CNFSM4DNCNYI2YY3PNVWWK3TUL52HS4DFVREXG43VMVBW63LNMVXHJKTDN5WW2ZLOORPWSZGOD2DWDMY#issuecomment-512188851>,
or mute the thread
<https://github.com/notifications/unsubscribe-auth/AANGRWS4E4LG3DOSX4SUFJLP73UYPANCNFSM4DNCNYIQ>
.
--
Ali Gajani
Founder at Mr. Geek
www.mrgeek.me
www.aligajani.com
|
I think, if you use index and have updateorcreate method, and put that inside try catch block, all is good. Imagine two threads both enter the if statement. So both of them are gonna write into the database. when one will start inserting it, the table will be locked for the other one. So first one gets inserted, and another one waits. when first is done and second one starts inserting, it's gonna throw exception of duplicate event entries. YOu got the catch block right? so you catch it and nothing is a problem. |
Is it possible to use |
@hxgdzyuyi Please see laravel/ideas#1090 (comment) why that is not a universal solution for |
how to avoid a duplicate entry in a db row? |
This worked for me, my primary 'id' key is the only unique key in my table, once I removed this from fillable assignment in model it worked. |
Is this solution valid ?:
|
is this fixed ? |
Problem still exists. Laravel 7.5.2 |
can confirm issue still present |
Please open a new issue with steps to reproduce. |
Description:
I want to save mobile devices information in the database using their UUIDs. The
uuid
field has a unique index. The code is:But I'm getting a lot of errors in logs:
I also tried to use firstOrNew:
But it's equal I guess.
If I remove the unique index from the
uuid
field the problem is gone but I get duplicate entries. That's strange because I thoughtupdateOrCreate
method guarantees uniqueness of the entries.Is it a bug of the
updateOrCreate
method?The text was updated successfully, but these errors were encountered: