n+1問題とincludesメソッド

#1. n+1問題について
n+1問題とは？
n+1問題とは、データベースからデータを取り出す際、大量のSQLが実行されて動作が重くなるという問題をいう。

大量のSQLが実行されてしまう理由は、テーブル間にアソシエーションが組まれているからである。具体的にいうと、一覧に表示するデータを取得するため、SELECTを1回実行すると、そのテーブルとアソシエーションが組まれている他のテーブルのデータにおいても、同時にSELECTが実行される。したがって、1回のSELECTでn回のSELECTが実行されてしまい、大量のSQLが実行されてしまうのである。
よって、テーブル間をbelongs_toやhas_manyでアソシエーションを組む際は、n+1問題を念頭に置かなければならない。
また、私見としては、n+1問題というよりも1+n問題と解釈した方がスッキリする。

##n+1問題の具体例

**モデル**

```ruby 
# 都道府県.
class Prefecture < ActiveRecord::Base
end
 
# 店舗.
class Shop < ActiveRecord::Base
  belongs_to :prefecture
end
```
**コントローラ**

```ruby
class ShopsController < ApplicationController
  def index
    @shops = Shop.order(:id)
  end
end
```

**ビュー**

```ruby
<h1>店舗一覧<h1>
 
<% @shops.each do |shop| %>
  <div><%= shop.name %> (<%= shop.prefecture.name %>)</div>
<% end %>
```
以上のプログラムを実行した後、ログファイルを覗いてみると、以下のようにprefectures (都道府県テーブル) へのSELECTが大量に出力されることになる。

```
Shop Load (0.7ms)  SELECT "shops".* FROM "shops" ORDER BY id LIMIT 20
  Prefecture Load (0.3ms)  SELECT "prefectures".* FROM "prefectures" WHERE "prefectures"."id" = 73 LIMIT 1
  CACHE (0.0ms)  SELECT "prefectures".* FROM "prefectures" WHERE "prefectures"."id" = 73 LIMIT 1
  Prefecture Load (0.4ms)  SELECT "prefectures".* FROM "prefectures" WHERE "prefectures"."id" = 126 LIMIT 1
  Prefecture Load (0.2ms)  SELECT "prefectures".* FROM "prefectures" WHERE "prefectures"."id" = 36 LIMIT 1
  Prefecture Load (0.2ms)  SELECT "prefectures".* FROM "prefectures" WHERE "prefectures"."id" = 26 LIMIT 1
  Prefecture Load (0.2ms)  SELECT "prefectures".* FROM "prefectures" WHERE "prefectures"."id" = 96 LIMIT 1
  Prefecture Load (0.2ms)  SELECT "prefectures".* FROM "prefectures" WHERE "prefectures"."id" = 52 LIMIT 1
  Prefecture Load (0.2ms)  SELECT "prefectures".* FROM "prefectures" WHERE "prefectures"."id" = 34 LIMIT 1
  Prefecture Load (0.2ms)  SELECT "prefectures".* FROM "prefectures" WHERE "prefectures"."id" = 47 LIMIT 1
  Prefecture Load (0.2ms)  SELECT "prefectures".* FROM "prefectures" WHERE "prefectures"."id" = 56 LIMIT 1
  CACHE (0.0ms)  SELECT "prefectures".* FROM "prefectures" WHERE "prefectures"."id" = 26 LIMIT 1
  Prefecture Load (0.2ms)  SELECT "prefectures".* FROM "prefectures" WHERE "prefectures"."id" = 12 LIMIT 1
  Prefecture Load (0.2ms)  SELECT "prefectures".* FROM "prefectures" WHERE "prefectures"."id" = 70 LIMIT 1
  Prefecture Load (0.2ms)  SELECT "prefectures".* FROM "prefectures" WHERE "prefectures"."id" = 31 LIMIT 1
  Prefecture Load (0.1ms)  SELECT "prefectures".* FROM "prefectures" WHERE "prefectures"."id" = 11 LIMIT 1
  Prefecture Load (0.2ms)  SELECT "prefectures".* FROM "prefectures" WHERE "prefectures"."id" = 109 LIMIT 1
  CACHE (0.0ms)  SELECT "prefectures".* FROM "prefectures" WHERE "prefectures"."id" = 34 LIMIT 1
  Prefecture Load (0.2ms)  SELECT "prefectures".* FROM "prefectures" WHERE "prefectures"."id" = 107 LIMIT 1
  Prefecture Load (0.2ms)  SELECT "prefectures".* FROM "prefectures" WHERE "prefectures"."id" = 114 LIMIT 1
  Prefecture Load (0.1ms)  SELECT "prefectures".* FROM "prefectures" WHERE "prefectures"."id" = 24 LIMIT 1
```

データベース上のデータが多ければ、多いほど出力されるデータも多くなる。

#2. includesメソッドについて

includesメソッドとは、関連づいたモデルを先に取得するメソッドをいう。
includesメソッドは、n+1問題の対処法に一つとして用いられる。

```ruby
@tweets = Tweet.includes(:user)
```
このように「**includes(:モデル名)**」と指定する。
こうすることで、tweetsモデルからデータを取得するときに、関連するusersモデルのデータもまとめて取得してくれる。
そのため、eachメソッドで一つ一つ表示する際も、すでに表示させるデータを**全て取得**しているので、その都度SQLを実行する必要はなくなり、n+1問題が生じなくなる。一覧表示をさせたい際、モデル間において、アソシエーションが組まれている場合は、includeメソッドを使用することをお勧めする。
　　　　　　　　　　　　　　　　　　　　　　　　　　　　　　以上