aboutsummaryrefslogtreecommitdiffstats
path: root/activerecord/lib/active_record/connection_adapters/abstract
diff options
context:
space:
mode:
authorJon Leighton <j@jonathanleighton.com>2012-09-21 16:14:42 +0100
committerJon Leighton <j@jonathanleighton.com>2012-09-21 16:32:27 +0100
commit392eeecc11a291e406db927a18b75f41b2658253 (patch)
tree985fa3eadbd3be740759568eb32d124eb86f6fbd /activerecord/lib/active_record/connection_adapters/abstract
parent834d6da54e459f6354fe7b349779d690652cc7a8 (diff)
downloadrails-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.rb63
-rw-r--r--activerecord/lib/active_record/connection_adapters/abstract/transaction.rb27
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