Spec-Zone.ru › Ruby on Rails 8.1

module ActiveRecord::Locking::Pessimistic

Пессимистическая блокировка

Locking::Pessimistic обеспечивает поддержку блокировок на уровне строк с помощью SELECT … FOR UPDATE и других типов блокировок.

Добавьте ActiveRecord::Base#find к ActiveRecord::QueryMethods#lock, чтобы получить исключительную блокировку выбранных строк:

# select * from accounts where id=1 for update
Account.lock.find(1)

Вызовите lock('some locking clause'), чтобы использовать собственную специфичную для базы данных конструкцию блокировки, например «LOCK IN SHARE MODE» или «FOR UPDATE NOWAIT». Пример:

Account.transaction do
  # select * from accounts where name = 'shugo' limit 1 for update nowait
  shugo = Account.lock("FOR UPDATE NOWAIT").find_by(name: "shugo")
  yuko = Account.lock("FOR UPDATE NOWAIT").find_by(name: "yuko")
  shugo.balance -= 100
  shugo.save!
  yuko.balance += 100
  yuko.save!
end

Также можно использовать метод ActiveRecord::Base#lock!, чтобы заблокировать одну запись по идентификатору. Это может быть предпочтительнее, если блокировать каждую строку не требуется. Пример:

Account.transaction do
  # select * from accounts where ...
  accounts = Account.where(...)
  account1 = accounts.detect { |account| ... }
  account2 = accounts.detect { |account| ... }
  # select * from accounts where id=? for update
  account1.lock!
  account2.lock!
  account1.balance -= 100
  account1.save!
  account2.balance += 100
  account2.save!
end

Можно начать транзакцию и сразу получить блокировку, вызвав with_lock с блоком. Блок вызывается внутри транзакции, а объект уже заблокирован. Пример:

account = Account.first
account.with_lock do
  # This block is called within a transaction,
  # account is already locked.
  account.balance -= 100
  account.save!
end

Сведения о блокировке строк для конкретных баз данных:

MySQL

dev.mysql.com/doc/refman/en/innodb-locking-reads.html

PostgreSQL

www.postgresql.org/docs/current/interactive/sql-select.html#SQL-FOR-UPDATE-SHARE

Открытые методы экземпляра

lock! (lock = true) Показать исходный код
# File activerecord/lib/active_record/locking/pessimistic.rb, line 69
      def lock!(lock = true)
        if self.class.current_preventing_writes
          raise ActiveRecord::ReadOnlyError, "Lock query attempted while in readonly mode"
        end

        if persisted?
          if has_changes_to_save?
            raise(<<-MSG.squish)
              Locking a record with unpersisted changes is not supported. Use
              `save` to persist the changes, or `reload` to discard them
              explicitly.
              Changed attributes: #{changed.map(&:inspect).join(', ')}.
            MSG
          end

          reload(lock: lock)
        end

        self
      end

Устанавливает блокировку строки для этой записи. Перезагружает запись, чтобы получить запрошенную блокировку. Передайте SQL-конструкцию блокировки, чтобы добавить её в конец оператора SELECT, либо передайте true для использования «FOR UPDATE» (по умолчанию; исключительная блокировка строки). Возвращает заблокированную запись.

with_lock (*args) { || ... } Показать исходный код
# File activerecord/lib/active_record/locking/pessimistic.rb, line 97
def with_lock(*args)
  transaction_opts = args.extract_options!
  lock = args.present? ? args.first : true
  transaction(**transaction_opts) do
    lock!(lock)
    yield
  end
end

Выполняет переданный блок внутри транзакции, предварительно перезагружая объект с блокировкой. В качестве необязательного аргумента можно передать SQL-конструкцию блокировки (см. lock!).

Также можно передать такие параметры, как requires_new:, isolation: и joinable:, во внешнюю транзакцию (см. ActiveRecord::ConnectionAdapters::DatabaseStatements#transaction).

© 2004–2021 David Heinemeier Hansson
Licensed under the MIT License.

Spec-Zone.ru

Настройки Оффлайн Что нового Помощь О нас
Spec-Zone .ru
спецификации, руководства, описания, API