简体   繁体   中英

transaction in activerecord

Folks,

I am fairly new to transactions in activerecord in rails and I have a piece of code, where I do something like:

transaction do
  specimen = Specimen.find_by_doc_id(25)
  specimen.state = "checking"
  specimen.save
  result = Inventory.do_check(specimen)
  if result
    specimen.state="PASS"
  else
    specimen.state="FAIL"
  end
  specimen.save
end

My goal here for using a transaction is if I get an exception in Inventory.do_check(it is a client to external web-services and does a bunch of HTTP calls and checks) then I want the specimen.state to rollback to its previous value. I wanted to know if this will work as above? Also, it looks like on my development machine the lock is set on the entire Specimen table, when I try to query that table/model I get a BUSY exception(I am using SQLLite). I was thinking that the lock should only be set on that object/record.

Any feedback is much appreciated, as I said I am really new to this so my question may be very naive.

Implementation and locking depends on the DB. I don't use SQLLite and I won't be surprised if it locks the entire table in such case. But reading should still work, so it's probably because it doesn't allow two concurrent operations on a single connection, so is waiting for your transaction to finish before allowing any other operation. See, for example, this SO answer: https://stackoverflow.com/a/7154699/2117020 .

However, my main point is you shouldn't be holding the transaction while accessing external services in any case. However it is implemented, keeping the transaction for seconds is not what you'd want. Looks like in your case all you want is to recover from an exception. Do you simply want to set the state to "FAIL" or "initial" as a result, or does do_check() modify your specimen? If do_check() doesn't modify the specimen, you should better do something like:

specimen = Specimen.find_by_doc_id(25)
specimen.state="checking"
specimen.save
# or simply specimen.update_attribute( :state, "checking" )

begin
  specimen.state = Inventory.do_check(specimen) ? "PASS" : "FAIL"
rescue
  specimen.state = "FAIL" # or "initial" or whatever
end
specimen.save

The locking is going to be highly dependent on your database. You could use a row lock. Something like this:

specimen = Specimen.find_by_doc_id(25)

success = true

# reloads the record and does a select for update which locks the row until the block exits (its wrapped in a transation)
specimen.with_lock do
  result = Inventory.do_check(specimen)
  if(result)
    specimen.state="PASS"
  else
    specimen.state="FAIL"
  end
  specimen.save!
end

Checking the external site in a transaction is not ideal, but if you use with_lock and your database supports row lock, you should just be locking this single row (it will block reads, so use carefully)

Take a look at the pessimistic locking documentation in active record: http://ruby-docs.com/docs/ruby_1.9.3-rails_3.2.2/Rails%203.2.2/classes/ActiveRecord/Locking/Pessimistic.html

The technical post webpages of this site follow the CC BY-SA 4.0 protocol. If you need to reprint, please indicate the site URL or the original address.Any question please contact:yoyou2525@163.com.

 
粤ICP备18138465号  © 2020-2024 STACKOOM.COM