Условия гонки в 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
, поэтому ваша реализация может быть другой.)