跳至内容 跳至搜索

悲观锁定

Locking::Pessimistic 提供对使用 SELECT … FOR UPDATE 和其他锁定类型的行级锁定的支持。

ActiveRecord::Base#findActiveRecord::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! 方法通过 ID 锁定一条记录。如果你不需要锁定每一行,这可能会更好。示例

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

方法
L
W

实例公共方法

lock!(lock = true)

获取此记录的行锁定。重新加载记录以获取请求的锁定。传递 SQL 锁定子句以追加到 SELECT 语句的末尾,或传递 true 以获取“FOR UPDATE”(默认值,独占行锁定)。返回锁定的记录。

# File activerecord/lib/active_record/locking/pessimistic.rb, line 69
      def lock!(lock = true)
        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

with_lock(*args)

将传递的块包装在一个事务中,在返回之前使用锁定重新加载对象。你可以将 SQL 锁定子句作为可选参数传递(参见 lock!)。

你还可以将选项(如 requires_new:isolation:joinable:)传递到包装事务(参见 ActiveRecord::ConnectionAdapters::DatabaseStatements#transaction)。

# File activerecord/lib/active_record/locking/pessimistic.rb, line 92
def with_lock(*args)
  transaction_opts = args.extract_options!
  lock = args.present? ? args.first : true
  transaction(**transaction_opts) do
    lock!(lock)
    yield
  end
end