悲观锁¶ ↑
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 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
with_lock(*args) 链接
将传递的块包装在一个事务中,在生成之前使用锁重新加载对象。您可以将 SQL 锁子句作为可选参数传递(请参阅 lock!)。
您还可以将 `requires_new:`, `isolation:`, 和 `joinable:` 等选项传递给包装事务(请参阅 ActiveRecord::ConnectionAdapters::DatabaseStatements#transaction)。
来源: 显示 | 在 GitHub 上
# 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