悲观锁
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!
方法通过 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
有关行锁的数据库特定信息
方法
实例公共方法
lock!(lock = true) 链接
获取此记录的行锁。重新加载记录以获得请求的锁。传递 SQL 锁定子句以追加到 SELECT 语句的末尾,或传递 true 以表示“FOR UPDATE”(默认值,独占行锁)。返回已锁定记录。
源代码:显示 | 在 GitHub 上
# 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) 链接
将传递的块包装在事务中,在 yield 之前使用锁重新加载对象。您可以将 SQL 锁定子句作为可选参数传递(参见 lock!
)。
您还可以将选项(如 requires_new:
、isolation:
和 joinable:
)传递给包装事务(参见 ActiveRecord::ConnectionAdapters::DatabaseStatements#transaction
)。
源代码:显示 | 在 GitHub 上
# 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