ActiveRecord Locking

If you have an application where data may be updated by more than one user at a time, you should consider concurrency issues and race conditions that may arise.  ActiveRecord supports both optimistic and pessimistic locking; there are pros and cons to both locking approaches, and it is to your advantage to know about them.

Optimistic Locking

When collisions are considered infrequent, optimistic locking is considered the way to go.  It takes the approach of leaving the record unlocked, but detects and resolves collisions if they occur.  Implementing this strategy is fairly trivial; add an integer column to the database called lock_version, with a default value of 0.  ActiveRecord’s behavior will change automatically for you; if two instances of the same record are loaded and saved differently, the first update will succeed, and the second update will raise an ActiveRecord::StaleObjectError.  You can customize the name of the locking column by calling the class method set_locking_column on your model, or globally in your environment.rb file using ActiveRecord::Base.set_locking_column = ‘alternate_lock’.  You will need to catch the raised exception and present the user with an appropriate message, etc.  The main disadvantage to using optimistic locking is that updates are a little slower; validation must be performed by checking the lock column prior to update.  The user also doesn’t find out about failure until after the attempt to save the data.  That could create an unpleasant user experience.

Pessimistic Locking

Database support is required for pessimistic locking to work.  The database is responsible for locking down specific rows during updates, effectively preventing other users from reading data that is about to be updated, rendering it impossible to read stale data.  Rails takes the approach of working with transactions as in the following example:

TripExpenseReport.transaction do
report = TripExpenseReport.find(1, :lock => true)
report.approved = true!

Alternatively, you could call an instance method called lock!, which calls reload( :lock => true ) behind the scenes.  Be sure to call lock! before making attribute changes, as they would obviously be lost by the call to reload.

When using pessimistic locking, the select statement that is generated will append FOR UPDATE to the sql, which causes all other connections to be blocked access to the rows returned by the query.  The lock is released once the transaction completes (is committed).  Beware of deadlocks; prevent it by capturing cases where a process fails to complete, etc.  Keep transactions small in scope and time to complete.

~ by bandwagonblog on January 24, 2009.

Leave a Reply

Fill in your details below or click an icon to log in: Logo

You are commenting using your account. Log Out / Change )

Twitter picture

You are commenting using your Twitter account. Log Out / Change )

Facebook photo

You are commenting using your Facebook account. Log Out / Change )

Google+ photo

You are commenting using your Google+ account. Log Out / Change )

Connecting to %s

%d bloggers like this: