Duplicate Records created by find_or_create_by_

2020-07-23 06:23发布

问题:

I have an ActiveRecord object, Corporation, and the only call in my project to create instances of this object looks like:

corp = Corporation.find_or_create_by_eveid_and_user_id(self.corporation_eveid, self.account.user_id)

Yet somehow, after my application has been running happily for a couple of days, there are duplicate records -- record where the eveid and user_id have the same values. How is this possible? Is there something I could be doing wrong in the way I update these records that would cause this problem?

I ended up added a unique, composite index to the table. That should solve the problem, but I don't understand how it's occurring.

This is Rails 3.0.7.

回答1:

find_or_create does not perform any locking and makes no attempt to prevent race conditions. It's just a convenience method. If race conditions are a problem, you will need to either:

  1. Use a transaction and roll back if you find somebody else has written just before you
  2. (Better if you're actually expecting a race condition), perform pessimistic locking. This is where you SELECT from the table acquiring an exclusive lock first, then perform the write and clear the lock. In MySQL InnoDB tables, this is SELECT ... FOR UPDATE. If you have no reference point to lock on (i.e. no foreign key or anything that already exists in the database), then you'll have to stick with (1).

EDIT | If you can add a UNIQUE constraint at the schema level, I'd advise doing so too, if this is a genuine integrity concern.



回答2:

Is this in your seeds file? Your best bet would be to write validations in your model to prevent the existence of a Corporation with the same eveid and user_id.

It seems to me that you seeded this information using find_or_create, which worked. But then maybe later in the day or another day someone created another one with the same information using your GUI. Validations would prevent this.

I have not tested this code, but something like this may work for you.

validates :eveid, :uniqueness => { :scope => :user_id }