https://api.rubyonrails.org/classes/ActiveRecord/Locking/Pessimistic.html

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 or FOR UPDATE NOWAIT. 例如:

Account.transaction do
  # select * from accounts where name = 'shugo' limit 1 for update
  shugo = Account.where("name = 'shugo'").lock(true).first
  yuko = Account.where("name = 'yuko'").lock(true).first
  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, 向其中传入一个 block , 会启动一个事务, 并获得锁. 整个 block 会被一个事务包装起来, 该对象获得了锁. 例如:

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: https://dev.mysql.com/doc/refman/5.7/en/innodb-locking-reads.html
PostgreSQL: https://www.postgresql.org/docs/current/interactive/sql-select.html#SQL-FOR-UPDATE-SHARE