Skip to content
Permalink
Browse files

Permit loads while queries are running

A query may wait on a database-level lock, which could lead to a
deadlock between threads.
  • Loading branch information
matthewd committed Oct 27, 2016
1 parent 928f466 commit 007e50d8e5a900547471b6c4ec79d9d217682c5d
@@ -215,7 +215,11 @@ def explain(arel, binds = [])

# Executes the SQL statement in the context of this connection.
def execute(sql, name = nil)
log(sql, name) { @connection.query(sql) }
log(sql, name) do
ActiveSupport::Dependencies.interlock.permit_concurrent_loads do
@connection.query(sql)
end
end
end

# Mysql2Adapter doesn't have to free a result after using it, but we use this method
@@ -86,7 +86,9 @@ def exec_stmt_and_free(sql, name, binds, cache_stmt: false)
end

begin
result = stmt.execute(*type_casted_binds)
ActiveSupport::Dependencies.interlock.permit_concurrent_loads do
result = stmt.execute(*type_casted_binds)
end
rescue Mysql2::Error => e
if cache_stmt
@statements.delete(sql)
@@ -85,7 +85,9 @@ def result_as_array(res) #:nodoc:
# Queries the database and returns the results in an Array-like object
def query(sql, name = nil) #:nodoc:
log(sql, name) do
result_as_array @connection.async_exec(sql)
ActiveSupport::Dependencies.interlock.permit_concurrent_loads do
result_as_array @connection.async_exec(sql)
end
end
end

@@ -95,7 +97,9 @@ def query(sql, name = nil) #:nodoc:
# need it specifically, you may want consider the <tt>exec_query</tt> wrapper.
def execute(sql, name = nil)
log(sql, name) do
@connection.async_exec(sql)
ActiveSupport::Dependencies.interlock.permit_concurrent_loads do
@connection.async_exec(sql)
end
end
end

@@ -601,15 +601,21 @@ def execute_and_clear(sql, name, binds, prepare: false)

def exec_no_cache(sql, name, binds)
type_casted_binds = type_casted_binds(binds)
log(sql, name, binds, type_casted_binds) { @connection.async_exec(sql, type_casted_binds) }
log(sql, name, binds, type_casted_binds) do
ActiveSupport::Dependencies.interlock.permit_concurrent_loads do
@connection.async_exec(sql, type_casted_binds)
end
end
end

def exec_cache(sql, name, binds)
stmt_key = prepare_statement(sql)
type_casted_binds = type_casted_binds(binds)

log(sql, name, binds, type_casted_binds, stmt_key) do
@connection.exec_prepared(stmt_key, type_casted_binds)
ActiveSupport::Dependencies.interlock.permit_concurrent_loads do
@connection.exec_prepared(stmt_key, type_casted_binds)
end
end
rescue ActiveRecord::StatementInvalid => e
raise unless is_cached_plan_failure?(e)
@@ -191,27 +191,29 @@ def exec_query(sql, name = nil, binds = [], prepare: false)
type_casted_binds = type_casted_binds(binds)

log(sql, name, binds, type_casted_binds) do
# Don't cache statements if they are not prepared
unless prepare
stmt = @connection.prepare(sql)
begin
cols = stmt.columns
unless without_prepared_statement?(binds)
stmt.bind_params(type_casted_binds)
ActiveSupport::Dependencies.interlock.permit_concurrent_loads do
# Don't cache statements if they are not prepared
unless prepare
stmt = @connection.prepare(sql)
begin
cols = stmt.columns
unless without_prepared_statement?(binds)
stmt.bind_params(type_casted_binds)
end
records = stmt.to_a
ensure
stmt.close
end
else
cache = @statements[sql] ||= {
stmt: @connection.prepare(sql)
}
stmt = cache[:stmt]
cols = cache[:cols] ||= stmt.columns
stmt.reset!
stmt.bind_params(type_casted_binds)
records = stmt.to_a
ensure
stmt.close
end
else
cache = @statements[sql] ||= {
stmt: @connection.prepare(sql)
}
stmt = cache[:stmt]
cols = cache[:cols] ||= stmt.columns
stmt.reset!
stmt.bind_params(type_casted_binds)
records = stmt.to_a
end

ActiveRecord::Result.new(cols, records)
@@ -229,7 +231,11 @@ def last_inserted_id(result)
end

def execute(sql, name = nil) #:nodoc:
log(sql, name) { @connection.execute(sql) }
log(sql, name) do
ActiveSupport::Dependencies.interlock.permit_concurrent_loads do
@connection.execute(sql)
end
end
end

def begin_db_transaction #:nodoc:

0 comments on commit 007e50d

Please sign in to comment.
You can’t perform that action at this time.