Условия гонки в Rails first_or_create

Я пытаюсь обеспечить уникальность значений в одном из полей таблицы. Изменение таблицы не является вариантом. Мне нужно использовать ActiveRecord, чтобы условно вставить строку в таблицу, но меня беспокоит синхронизация.

Предоставляет ли first_or_create в Rails ActiveRecord предотвращение условий гонки?

Это исходный код для first_or_create из GitHub:

def first_or_create(attributes = nil, options = {}, &block)
  first || create(attributes, options, &block)
end

Возможно ли, что дублирующаяся запись приведет к базе данных из-за проблем синхронизации с несколькими процессами?

Ответы

Ответ 1

Да, это возможно.

Вы можете значительно уменьшить вероятность конфликта с помощью optimistic или пессимистическая блокировка. Конечно, оптимистическая блокировка требует добавления поля в таблицу, а пессимистическая блокировка также не масштабируется - плюс, это зависит от ваших возможностей хранения данных.

Я не уверен, нужна ли вам дополнительная защита, но она доступна.

Ответ 2

Документация Rails 4 для find_or_create_by содержит подсказку, которая может быть полезна для этой ситуации:

Обратите внимание, что этот метод не является атомарным, он запускает сначала SELECT, и если нет результатов, то попытка INSERT будет предпринята. Если есть другие потоки или процессы, между обеими вызовами есть условие гонки, и это может привести к тому, что вы получите две аналогичные записи.

Является ли это проблемой или нет, зависит от логики приложения, но в конкретном случае, когда строки имеют ограничение UNIQUE, может быть возбуждено исключение, просто повторите попытку:

begin
  CreditAccount.find_or_create_by(user_id: user.id)
rescue ActiveRecord::RecordNotUnique
  retry
end

Подобная уловка ошибок может быть полезна для Rails 3. (Не уверен, что в Rails 3 возникает одна и та же ошибка ActiveRecord::RecordNotUnique, поэтому ваша реализация может быть другой.)