Module: ActiveRecord::Locking::Pessimistic
Relationships & Source Files | |
Extension / Inclusion / Inheritance Descendants | |
Included In:
| |
Defined in: | activerecord/lib/active_record/locking/pessimistic.rb |
Overview
Pessimistic
provides support for row-level locking using SELECT … FOR UPDATE and other lock types.
Chain ActiveRecord::Base#find
to QueryMethods#lock to obtain an exclusive lock on the selected rows:
# select * from accounts where id=1 for update
Account.lock.find(1)
Call lock('some locking clause')
to use a database-specific locking clause of your own such as ‘LOCK IN SHARE MODE’ or ‘FOR UPDATE NOWAIT’. Example:
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
You can also use ActiveRecord::Base#lock!
method to lock one record by id. This may be better if you don’t need to lock every row. Example:
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
You can start a transaction and acquire the lock in one go by calling #with_lock with a block. The block is called from within a transaction, the object is already locked. Example:
account = Account.first
account.with_lock do
# This block is called within a transaction,
# account is already locked.
account.balance -= 100
account.save!
end
Database-specific information on row locking:
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
Instance Method Summary
-
#lock!(lock = true)
Obtain a row lock on this record.
-
#with_lock(lock = true)
Wraps the passed block in a transaction, locking the object before yielding.
Instance Method Details
#lock!(lock = true)
Obtain a row lock on this record. Reloads the record to obtain the requested lock. Pass an SQL locking clause to append the end of the SELECT statement or pass true for “FOR UPDATE” (the default, an exclusive row lock). Returns the locked record.
# File 'activerecord/lib/active_record/locking/pessimistic.rb', line 63
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. MSG end reload(lock: lock) end self end
#with_lock(lock = true)
Wraps the passed block in a transaction, locking the object before yielding. You can pass the SQL locking clause as argument (see #lock!).
# File 'activerecord/lib/active_record/locking/pessimistic.rb', line 81
def with_lock(lock = true) transaction do lock!(lock) yield end end