Performance Optimization with Raw SQL Queries in Rails

Nov 20, 2025 · Programming · 9 views · 7.8

Keywords: Rails Raw SQL | Performance Optimization | ActiveRecord Queries

Abstract: This technical article provides an in-depth analysis of using raw SQL queries in Ruby on Rails applications to address performance bottlenecks. Focusing on timeout errors encountered during Heroku deployment, the article explores core implementation methods including ActiveRecord::Base.connection.execute and find_by_sql, compares their result data structures, and presents comprehensive code examples with best practices. Security considerations and appropriate use cases for raw SQL queries are thoroughly discussed to help developers balance performance gains with code maintainability.

Problem Context and Performance Challenges

During Ruby on Rails application development, developers frequently encounter scenarios where ActiveRecord queries exhibit insufficient performance in specific environments. Particularly when deploying to cloud platforms like Heroku, complex association queries can lead to request timeout errors. The original problem demonstrates this challenge clearly:

@payments = PaymentDetail.joins(:project).order('payment_details.created_at desc')
@payment_errors = PaymentError.joins(:project).order('payment_errors.created_at desc')
@all_payments = (@payments + @payment_errors)

This implementation involves multiple database queries and in-memory array merging operations, which can become performance bottlenecks with large datasets.

Core Implementation of Raw SQL Queries

Rails provides multiple methods for executing raw SQL queries, with ActiveRecord::Base.connection.execute being the most direct approach. This method completely bypasses ActiveRecord's query builder and sends SQL statements directly to the database:

sql = "SELECT * FROM payment_details pd 
INNER JOIN projects p ON pd.project_id = p.id 
UNION ALL 
SELECT * FROM payment_errors pe 
INNER JOIN projects p ON pe.project_id = p.id 
ORDER BY created_at DESC"
records_array = ActiveRecord::Base.connection.execute(sql)

The execution result records_array is an array containing query results, which developers can directly iterate through and process.

Alternative Approaches and Result Handling

Beyond the basic execute method, Rails offers the find_by_sql approach, which automatically wraps query results into ActiveRecord objects:

@all_payments = PaymentDetail.find_by_sql("
  SELECT pd.* FROM payment_details pd 
  INNER JOIN projects p ON pd.project_id = p.id 
  UNION ALL 
  SELECT pe.* FROM payment_errors pe 
  INNER JOIN projects p ON pe.project_id = p.id 
  ORDER BY created_at DESC
")

This approach maintains ActiveRecord object characteristics while benefiting from raw SQL performance advantages. For scenarios requiring more flexible result handling, the connection.select_all method can be employed:

result = ActiveRecord::Base.connection.select_all(sql)
hashed_results = result.to_hash
column_names = result.columns
row_values = result.rows

Performance Optimization and Security Considerations

The primary reason raw SQL queries can significantly improve performance lies in reducing database round trips and query optimizer overhead. In the original problem, combining two separate queries into a single UNION query avoids in-memory array merging operations.

However, using raw SQL requires special attention to SQL injection security concerns. All user inputs must undergo proper parameterization:

# Unsafe approach
sql = "SELECT * FROM users WHERE name = '#{params[:name]}'"

# Safe parameterized query
sql = "SELECT * FROM users WHERE name = ?"
records = ActiveRecord::Base.connection.execute(sql, params[:name])

Practical Application Scenarios Analysis

Raw SQL queries are particularly suitable for: complex multi-table join queries, queries requiring database-specific features (such as window functions, recursive queries), batch data processing operations, and performance-critical report generation.

An important concept highlighted in the reference material is the flexibility of query results. Through the find_by_sql method, even when queries involve fields from other tables, results are still wrapped in instances of the specified model. This characteristic proves valuable in scenarios requiring object consistency.

Best Practices Recommendations

When implementing raw SQL queries in practical projects, we recommend following these best practices: encapsulate complex SQL queries within model methods to maintain clear business logic; write unit tests for important SQL queries to ensure functional correctness; use database EXPLAIN commands to analyze query performance; add appropriate comments in code explaining the rationale for using raw SQL and query logic.

By judiciously applying raw SQL queries, developers can maintain Rails development efficiency while addressing specific performance bottlenecks, achieving significant application performance improvements.

Copyright Notice: All rights in this article are reserved by the operators of DevGex. Reasonable sharing and citation are welcome; any reproduction, excerpting, or re-publication without prior permission is prohibited.