简体   繁体   中英

Rails + ActiveRecord - fighting with “N+1” issue

I have these models:

car:

class Car < ActiveRecord::Base
  has_many :car_services, dependent: :destroy
  has_many :services, through: :car_services
end

car_service

class CarService < ActiveRecord::Base
  belongs_to :car
  belongs_to :service
  validates_uniqueness_of :service_id, scope: :car_id
end

truck

class Truck < ActiveRecord::Base 
  has_many :truck_services, dependent: :destroy
  has_many :services, through: :truck_services
end

truck_service

class TruckService < ActiveRecord::Base
  belongs_to :truck
  belongs_to :service
end

service

class Service < ActiveRecord::Base
  has_many :car_services
  has_many :cars, through: :car_services

  has_many :truck_services
  has_many :trucks, through: :truck_services
end

In controllers' actions (there are separated actions for cars and for trucks) I query the models like this:

@cars = Car.find_by_sql("SELECT ...")
...
@trucks = Truck.find_by_sql("SELECT ...")

and then in the respective views:

<% @cars.each do |result| %>
  <li data-services="<% result.services.each do |service| %><%= service.name %>,<% end %>">...</li>
  ...
<% end %>

This procedure works very well for cars , it's very fast.

Different action for trucks , but the same procedure:

<% @trucks.each do |result| %>
  <li data-services="<% result.services.each do |service| %><%= service.name %>,<% end %>">...</li>
  ...
<% end %>

Now, this procedure is very slow. When I tried to debug why (because as I mentioned, the procedure is same for both models, but for cars it's working very fast even though that in the car model is 600k records and in the truck "only" 300k), I spotted in the console log following information:

...
Service Load (159.1ms)  SELECT `services`.* FROM `services` INNER JOIN `truck_services` ON `services`.`id` = `truck_services`.`service_id` WHERE `truck_services`.`truck_id` = 35769
Service Load (166.8ms)  SELECT `services`.* FROM `services` INNER JOIN `truck_services` ON `services`.`id` = `truck_services`.`service_id` WHERE `truck_services`.`truck_id` = 45681
Service Load (151.4ms)  SELECT `services`.* FROM `services` INNER JOIN `truck_services` ON `services`.`id` = `truck_services`.`service_id` WHERE `truck_services`.`truck_id` = 50974
...

How is this possible? Why I don't see these lines also for cars and I see them for truck ? Do I miss something? I was thinking about missing indexes in the MySQL tables, but there aren't used any.

I am fighting with this issue the whole Saturday, but I have no solution for this...

I would be very grateful for an advice how to fix this issue.

Thank you very much guys.

I will start with an advice : post your sql queries for others to understand exactly what you are doing and to be able to help you

To eliminate N+1 you should use includes or preload

In your controllers do this:

@cars = Car.includes(:services).all

respectively,

@trucks = Truck.includes(:services).all

You can see that I preferred a much cleaner syntax to find_by_sql.

When you run:

@cars = Car.includes(:services).all

this will trigger 3 queries:

SELECT "cars".* FROM "cars";
SELECT "car_services".* FROM "car_services" WHERE "car_services".car_id IN (?, ?, ? ...);
SELECT "services".* FROM "services" WHERE "services".car_service_id IN (?, ?, ? ...);

If you remove N+1 your page will load faster but you should check that all your keys have indexes defined, too.

The above its just an example. You should really post your complete queries for us to understand exactly what you are doing. And those models Car and Truck, they look very similar, maybe it will be a better solution to use Single Table Inheritance .

You could rewrite this:

<% @trucks.each do |result| %>
  <li data-services="<% result.services.each do |service| %><%= service.name %>,<% end %>">...</li>
<% end %>

as this

<% @trucks.each do |truck| %>
  <%= content_tag :li, '...', data: {services: truck.services.map(&:name).join(', ')} %>
<% end %>

Hope this will help you somehow.

The technical post webpages of this site follow the CC BY-SA 4.0 protocol. If you need to reprint, please indicate the site URL or the original address.Any question please contact:yoyou2525@163.com.

 
粤ICP备18138465号  © 2020-2024 STACKOOM.COM