diff options
author | Jon Leighton <j@jonathanleighton.com> | 2012-09-21 16:14:42 +0100 |
---|---|---|
committer | Jon Leighton <j@jonathanleighton.com> | 2012-09-21 16:32:27 +0100 |
commit | 392eeecc11a291e406db927a18b75f41b2658253 (patch) | |
tree | 985fa3eadbd3be740759568eb32d124eb86f6fbd /activerecord/lib/active_record/connection_adapters/abstract | |
parent | 834d6da54e459f6354fe7b349779d690652cc7a8 (diff) | |
download | rails-392eeecc11a291e406db927a18b75f41b2658253.tar.gz rails-392eeecc11a291e406db927a18b75f41b2658253.tar.bz2 rails-392eeecc11a291e406db927a18b75f41b2658253.zip |
Support for specifying transaction isolation level
If your database supports setting the isolation level for a transaction,
you can set it like so:
Post.transaction(isolation: :serializable) do
# ...
end
Valid isolation levels are:
* `:read_uncommitted`
* `:read_committed`
* `:repeatable_read`
* `:serializable`
You should consult the documentation for your database to understand the
semantics of these different levels:
* http://www.postgresql.org/docs/9.1/static/transaction-iso.html
* https://dev.mysql.com/doc/refman/5.0/en/set-transaction.html
An `ActiveRecord::TransactionIsolationError` will be raised if:
* The adapter does not support setting the isolation level
* You are joining an existing open transaction
* You are creating a nested (savepoint) transaction
The mysql, mysql2 and postgresql adapters support setting the
transaction isolation level. However, support is disabled for mysql
versions below 5, because they are affected by a bug
(http://bugs.mysql.com/bug.php?id=39170) which means the isolation level
gets persisted outside the transaction.
Diffstat (limited to 'activerecord/lib/active_record/connection_adapters/abstract')
-rw-r--r-- | activerecord/lib/active_record/connection_adapters/abstract/database_statements.rb | 63 | ||||
-rw-r--r-- | activerecord/lib/active_record/connection_adapters/abstract/transaction.rb | 27 |
2 files changed, 75 insertions, 15 deletions
diff --git a/activerecord/lib/active_record/connection_adapters/abstract/database_statements.rb b/activerecord/lib/active_record/connection_adapters/abstract/database_statements.rb index 32e3c7f5d8..793f58d4d3 100644 --- a/activerecord/lib/active_record/connection_adapters/abstract/database_statements.rb +++ b/activerecord/lib/active_record/connection_adapters/abstract/database_statements.rb @@ -155,10 +155,47 @@ module ActiveRecord # # active_record_1 now automatically released # end # RELEASE SAVEPOINT active_record_1 <--- BOOM! database error! # end + # + # == Transaction isolation + # + # If your database supports setting the isolation level for a transaction, you can set + # it like so: + # + # Post.transaction(isolation: :serializable) do + # # ... + # end + # + # Valid isolation levels are: + # + # * <tt>:read_uncommitted</tt> + # * <tt>:read_committed</tt> + # * <tt>:repeatable_read</tt> + # * <tt>:serializable</tt> + # + # You should consult the documentation for your database to understand the + # semantics of these different levels: + # + # * http://www.postgresql.org/docs/9.1/static/transaction-iso.html + # * https://dev.mysql.com/doc/refman/5.0/en/set-transaction.html + # + # An <tt>ActiveRecord::TransactionIsolationError</tt> will be raised if: + # + # * The adapter does not support setting the isolation level + # * You are joining an existing open transaction + # * You are creating a nested (savepoint) transaction + # + # The mysql, mysql2 and postgresql adapters support setting the transaction + # isolation level. However, support is disabled for mysql versions below 5, + # because they are affected by a bug[http://bugs.mysql.com/bug.php?id=39170] + # which means the isolation level gets persisted outside the transaction. def transaction(options = {}) - options.assert_valid_keys :requires_new, :joinable + options.assert_valid_keys :requires_new, :joinable, :isolation if !options[:requires_new] && current_transaction.joinable? + if options[:isolation] + raise ActiveRecord::TransactionIsolationError, "cannot set isolation when joining a transaction" + end + yield else within_new_transaction(options) { yield } @@ -168,10 +205,10 @@ module ActiveRecord end def within_new_transaction(options = {}) #:nodoc: - begin_transaction(options) + transaction = begin_transaction(options) yield rescue Exception => error - rollback_transaction + rollback_transaction if transaction raise ensure begin @@ -191,9 +228,7 @@ module ActiveRecord end def begin_transaction(options = {}) #:nodoc: - @transaction = @transaction.begin - @transaction.joinable = options.fetch(:joinable, true) - @transaction + @transaction = @transaction.begin(options) end def commit_transaction #:nodoc: @@ -217,6 +252,22 @@ module ActiveRecord # Begins the transaction (and turns off auto-committing). def begin_db_transaction() end + def transaction_isolation_levels + { + read_uncommitted: "READ UNCOMMITTED", + read_committed: "READ COMMITTED", + repeatable_read: "REPEATABLE READ", + serializable: "SERIALIZABLE" + } + end + + # Begins the transaction with the isolation level set. Raises an error by + # default; adapters that support setting the isolation level should implement + # this method. + def begin_isolated_db_transaction(isolation) + raise ActiveRecord::TransactionIsolationError, "adapter does not support setting transaction isolation" + end + # Commits the transaction (and turns on auto-committing). def commit_db_transaction() end diff --git a/activerecord/lib/active_record/connection_adapters/abstract/transaction.rb b/activerecord/lib/active_record/connection_adapters/abstract/transaction.rb index 2117eae5cb..4cca94e40b 100644 --- a/activerecord/lib/active_record/connection_adapters/abstract/transaction.rb +++ b/activerecord/lib/active_record/connection_adapters/abstract/transaction.rb @@ -13,8 +13,8 @@ module ActiveRecord 0 end - def begin - RealTransaction.new(connection, self) + def begin(options = {}) + RealTransaction.new(connection, self, options) end def closed? @@ -38,13 +38,13 @@ module ActiveRecord attr_reader :parent, :records attr_writer :joinable - def initialize(connection, parent) + def initialize(connection, parent, options = {}) super connection @parent = parent @records = [] @finishing = false - @joinable = true + @joinable = options.fetch(:joinable, true) end # This state is necesarry so that we correctly handle stuff that might @@ -66,11 +66,11 @@ module ActiveRecord end end - def begin + def begin(options = {}) if finishing? parent.begin else - SavepointTransaction.new(connection, self) + SavepointTransaction.new(connection, self, options) end end @@ -120,9 +120,14 @@ module ActiveRecord end class RealTransaction < OpenTransaction #:nodoc: - def initialize(connection, parent) + def initialize(connection, parent, options = {}) super - connection.begin_db_transaction + + if options[:isolation] + connection.begin_isolated_db_transaction(options[:isolation]) + else + connection.begin_db_transaction + end end def perform_rollback @@ -137,7 +142,11 @@ module ActiveRecord end class SavepointTransaction < OpenTransaction #:nodoc: - def initialize(connection, parent) + def initialize(connection, parent, options = {}) + if options[:isolation] + raise ActiveRecord::TransactionIsolationError, "cannot set transaction isolation in a nested transaction" + end + super connection.create_savepoint end |