Могут ли экземпляры модели в приложении Rails быть изменены другими подключениями к БД во время их проверки?

Я углубляюсь в Rails 4 и пытаюсь понять, как безопасно получить доступ к данным модели, когда они доступны через несколько соединений с БД. У меня есть некоторая логика создания совпадений, которая находит самого старого пользователя в очереди, удаляет этого пользователя из очереди и возвращает пользователя...

# UserQueue.rb
class UserQueue < ActiveRecord::Base
  has_many :users

  def match_user(user)
    match = nil
    if self.users.count > 0
      oldest = self.users.oldest_in_queue 
      if oldest.id != user.id
        self.users.delete(oldest)
        match = oldest
      end
    end
  end
end

Если два разных потока выполнили это match_user метод в то же время, могут ли они найти одинаковые oldest пользователь и попытаться удалить его из очереди, и вернуть его вызывающей стороне? Если так, как я могу предотвратить это?

Я посмотрел на транзакции, но они, похоже, не являются решением, так как в этом случае изменяется только одна модель (очередь).

Заранее спасибо за вашу мудрость!

2 ответа

Решение

ActiveRecord поддерживает блокировку строк.

Это взято из руководства по Rails, блокирующего записи для обновления:

11.1 Оптимистическая блокировка

Оптимистическая блокировка позволяет нескольким пользователям получать доступ к одной и той же записи для редактирования и предполагает минимум конфликтов с данными. Это делается путем проверки того, внес ли другой процесс изменения в запись с момента ее открытия. ActiveRecord::StaleObjectError Выдается исключение, если это произошло, и обновление игнорируется.

Оптимистическая блокировка колонны

Чтобы использовать оптимистическую блокировку, в таблице должен быть столбец с именем lock_version типа целое число. Каждый раз, когда запись обновляется, активная запись увеличивает lock_version колонка. Если запрос на обновление сделан с более низким значением в lock_version поле, чем в настоящее время в lock_version столбец в базе данных, запрос на обновление завершится неудачно с ActiveRecord::StaleObjectError, Пример:

c1 = Client.find(1)
c2 = Client.find(1)

c1.first_name = "Michael"
c1.save

c2.name = "should fail"
c2.save # Raises an ActiveRecord::StaleObjectError

Затем вы отвечаете за решение конфликта, спасая исключение и откатывая, объединяя или иным образом применяя бизнес-логику, необходимую для разрешения конфликта.

Это поведение можно отключить, установив ActiveRecord::Base.lock_optimistically = false,

Переопределить имя lock_version колонка, ActiveRecord::Base предоставляет атрибут класса с именем locking_column:

class Client < ActiveRecord::Base
  self.locking_column = :lock_client_column
end

Я предлагаю прочитать этот раздел в руководстве по Rails.

Да, это может произойти. Как вы предотвратите это, зависит от остальной части вашего приложения /framework/database/etc.

Транзакции не помогут, потому что два клиента могут запустить запрос одновременно, и оба увидят одну и ту же запись UserQueue как самую старую.

Вы хотите мьютекс. Но мьютекс в коде не идеален, если есть другие способы изменения данных (т. Е. Напрямую через SQL и т. Д.). Он также может запутаться, так как в первый раз, когда вы забудете использовать мьютекс, вы открыли состояние гонки. Опять же, может быть, этого достаточно. Просто помните, что ваш мьютекс должен работать между потоками и процессами и т. Д.

Вы можете увидеть, есть ли в вашей базе данных мьютекс или другая блокировка на уровне строк, которую вы можете использовать, чтобы отметить самую старую запись очереди, а затем извлечь ее.

Или найдите другой способ получить самую старую очередь, которая полностью исключает условия гонки. Что-то вроде:

  • SQL для обновления самой старой очереди с идентификатором, отличным от user.id, и установкой "mark_for_work" на некоторый уникальный идентификатор.
  • Получите строку очереди, чье значение отмеченное_для_работы является нашим уникальным идентификатором.

Вы можете без проблем запускать несколько потоков с помощью описанного выше, поскольку обновление SQL (ну, оно должно быть!) Атомарным.

Другие вопросы по тегам