0
0

Delete article

Deleted articles cannot be recovered.

Draft of this article would be also deleted.

Are you sure you want to delete this article?

More than 3 years have passed since last update.

[Rails]N+1問題の解決策

Posted at

問題が発生している様子

画面を読み込んでいるときのターミナル

Terminal
Processing by PagesController#index as HTML
  Rendering layout layouts/application.html.slim
  Rendering pages/index.html.slim within layouts/application
  Product Load (0.9ms)  SELECT `products`.* FROM `products`
   app/views/pages/index.html.slim:25
  Brand Load (0.9ms)  SELECT `brands`.* FROM `brands` WHERE `brands`.`id` = 1 LIMIT 1
   app/views/pages/index.html.slim:30
  Brand Load (0.9ms)  SELECT `brands`.* FROM `brands` WHERE `brands`.`id` = 12 LIMIT 1
   app/views/pages/index.html.slim:30
  Brand Load (0.8ms)  SELECT `brands`.* FROM `brands` WHERE `brands`.`id` = 15 LIMIT 1
   app/views/pages/index.html.slim:30
  Brand Load (0.9ms)  SELECT `brands`.* FROM `brands` WHERE `brands`.`id` = 2 LIMIT 1
   app/views/pages/index.html.slim:30
  Brand Load (0.8ms)  SELECT `brands`.* FROM `brands` WHERE `brands`.`id` = 6 LIMIT 1
   app/views/pages/index.html.slim:30
  Brand Load (0.8ms)  SELECT `brands`.* FROM `brands` WHERE `brands`.`id` = 3 LIMIT 1
   app/views/pages/index.html.slim:30
  Rendered pages/index.html.slim within layouts/application (Duration: 44.1ms | Allocations: 14640)

複数のクエリが発行されている。
最初にproductsテーブル中の全てを取り出すのに1回、その中からbrandを6回取り出している。

これは今後クエリ発生数が、何万回のように膨大になると動作が悪化するので対策をとる。

解決策

Controllerで、includesメソッドを使用する。(N+1問題が発生している箇所)
自分はallメソッドを使用していた。

app/controllers/pages_controller.rb
class PagesController < ApplicationController
  def index
    - @products = Product.all
    + @products = Product.includes(:brand)
  end
end
includeメソッド基礎構文
モデル名.include(:関連名)
  • 関連名 ≠ テーブル名 (下記 app/models/product.rb 参照)

対策した結果

Terminal
Processing by PagesController#index as HTML
  Rendering layout layouts/application.html.slim
  Rendering pages/index.html.slim within layouts/application
  Product Load (0.9ms)  SELECT `products`.* FROM `products`
   app/views/pages/index.html.slim:25
  Brand Load (1.1ms)  SELECT `brands`.* FROM `brands` WHERE `brands`.`id` IN (1, 12, 15, 2, 6, 3)
   app/views/pages/index.html.slim:25
  Rendered pages/index.html.slim within layouts/application (Duration: 24.3ms | Allocations: 10596)

Brandに関するクエリの発生数が一回になった。

その他詳細

コード

View

index.html.slim
スクリーンショット 2021-04-17 13.01.14.png

app/views/pages/index.html.slim
.container
  .row
    .col-12
      table.table.table-hover.table--index-product
        thead
          (省略)
        tbody
          - @products.each do |product|
            tr
              th scope="row"
              td
                p = link_to product.name, product
                p = link_to product.brand.name, product.brand
              td.d-none.d-md-table-cell
                p = link_to product.battery_capacity, product
              td
                p = link_to product.soc_antutu_score, product

Model

  • ER図
    スクリーンショット 2021-04-17 15.17.09.png
    複数のProductは、一つのBrandに属している。
    Productテーブルに対して、Brandは必ず1レコード存在させる。
app/models/brand.rb
class Brand < ApplicationRecord
  has_many :products
end
app/models/product.rb
class Product < ApplicationRecord
  belongs_to :brand #関連名
  validates :brand_id, presence: true
end

・belongs_toメソッドの引数の関連名は単数形

Controller

Pages_controller.rb
class PagesController < ApplicationController
  def index
    @products = Product.includes(:brand)
  end
end

参考URL

includesメソッド

ER図

0
0
0

Register as a new user and use Qiita more conveniently

  1. You get articles that match your needs
  2. You can efficiently read back useful information
  3. You can use dark theme
What you can do with signing up
0
0

Delete article

Deleted articles cannot be recovered.

Draft of this article would be also deleted.

Are you sure you want to delete this article?