[ORM] Eager Loading and N+1 Query Problem
What is N+1 query problem?
When your code loads the children in a parent-child relationship via the parent, most ORM have lazy-loading enabled by default, so queries are issued for the parent record, and then one query for EACH child record. As you can expect, doing N + 1 queries instead of a single query will floor your database with queries; something to avoid.
Consider this code:
#Articles model
class Article < ActiveRecord::Base
belongs_to :author
end
#Authors model
class Author < ActiveRecord::Base
has_many :posts
end
If you then ran:
#In our controller
@recent_articles = Article.order(published_at: :desc).limit(5)
#in our view file
@recent_articles.each do |article|
Title: <%= article.title %>
Author:<%= article.author.name %> # <-- this will trigger N+1 query
end
You would send 6 (5+1) queries to the database. 1 to fetch 5 recent articles, and then 5 for their corresponding authors.
Remember: it is faster to send 1 query which returns 100 results, than to issue 100 queries which returns 1 result.
Solution - Eager Loading
In Rails, ActiveRecord has a method called
includes
, which ensures that all the associated datas specified are loaded with the minimum number of queries.Eager loading means to fetch all the data ahead of time. Instead of accessing EACH child id and calling a single query, it will fetch the ids all the child and then use a single query to get data of all the child of interest.
Before Eager Loading:
Article Load (0.9ms) SELECT 'articles'.* FROM 'articles'
Author Load (0.4ms) SELECT 'authors'.* FROM 'authors' WHERE 'authors'.'id' = ? ORDER BY 'authors'.'id' ASC LIMIT 1 [["id", 1]]
Author Load (0.3ms) SELECT 'authors'.* FROM 'authors' WHERE 'authors'.'id' = ? ORDER BY 'authors'.'id' ASC LIMIT 1 [["id", 2]]
Author Load (0.4ms) SELECT 'authors'.* FROM 'authors' WHERE 'authors'.'id' = ? ORDER BY 'authors'.'id' ASC LIMIT 1 [["id", 3]]
Author Load (0.3ms) SELECT 'authors'.* FROM 'authors' WHERE 'authors'.'id' = ? ORDER BY 'authors'.'id' ASC LIMIT 1 [["id", 4]]
Author Load (0.4ms) SELECT 'authors'.* FROM 'authors' WHERE 'authors'.'id' = ? ORDER BY 'authors'.'id' ASC LIMIT 1 [["id", 5]]
After Eager Loading:
Article Load (0.4ms) SELECT 'articles'.* FROM 'articles'
Author Load (0.4ms) SELECT 'authors'.* FROM 'authors' WHERE 'authors'.'id' IN (1,2,3,4,5)
Remember: less query = better.
Comments
Post a Comment