What is it?ExampleHow to prevent it?Eager LoadLazy LoadOver-eager LoadSolve it in RailsPrerequisitesSolutionSource
What is it?
- performance issues as it results in multiple database queries
- Usually solved by eager loading
Example
Let's say you have a collection ofCar
objects (database rows), and eachCar
has a collection ofWheel
objects (also rows). In other words,Car
→Wheel
is a 1-to-many relationship.
Now, let's say you need to iterate through all the cars, and for each one, print out a list of the wheels. The naive O/R implementation would do the following:
SELECT * FROM Cars;
And then for each Car
:
SELECT * FROM Wheel WHERE CarId = ?
In other words, you have one select for the Cars, and then N additional selects, where N is the total number of cars.
Alternatively, one could get all wheels and perform the lookups in memory:
SELECT * FROM Wheel;
This reduces the number of round-trips to the database from N+1 to 2. Most ORM tools give you several ways to prevent N+1 selects.
Reference: Java Persistence with Hibernate, chapter 13.
Answered by Matt Solnit in https://stackoverflow.com/questions/97197/what-is-the-n1-selects-problem-in-orm-object-relational-mapping
How to prevent it?
Eager Load
- Load everything asked
- Ex: the example above uses eager loading by querying every wheels into the memory first
Lazy Load
- Load partially and required
- Ex: the browser only loads the web page when users scroll down
- Not efficient to DB because too many queries, but good to use when not enough memory on the client side
Over-eager Load
- Anticipate what users need and preload it
Solve it in Rails
Prerequisites
There must be
has_one
, has_many
, and/or belongs_to
in two associated models.Solution
From ChatGPT:
includes
:
Example:
@users = User.includes(:posts)
# Only two queries
# SELECT * FROM users
# SELECT * FROM posts WHERE posts.id IN (users.post_ids)
includes
also supports filtering using where
.joins
:
Example:
@users = User.joins(:posts).where(posts: { published: true })
# One query but it is inner join
# SELECT * FROM users INNER JOIN posts ON posts.id = users.post_ids WHERE posts.published IS true
preload
andeager_load
: In addition toincludes
, ActiveRecord providespreload
andeager_load
methods that offer different strategies for eager loading.
Example:
@users = User.preload(:posts)
# Only two queries
# SELECT * FROM users
# SELECT * FROM posts WHERE posts.id IN (users.post_ids)