n+1問題の解決方法

n+1問題とは


2つのテーブルがあってアソシエーションによって関連させている時、データの読み込みを行うとSQL文が余分に発行されてしまう現象で、大量のプログラムをアプリが所有していると読み込みに時間がかかってしまう問題のこと。

例えば、usersテーブルとtweetsテーブルの2つがありそれぞれが関連状態にあります。

モデルに記載された関係は、下のコードになります。

#userはtweetsに対して1対多の関係
has_many :tweets
#tweetはuserに対して多対1の関係
belongs_to :user

詳しくはアソシエーションを参考にしてください。

以下のプログラムが動作し、データベースからデータを取り出しHTMLに変換するとします。

<% @tweets.each do |tweet| %>
  <div class="content_post">
   <%= simpleformat(tweet.text) %>
  </div>
<% end %>

すると、SQLでは以下のような処理が実行される。

# tweetsテーブルの全てのツイートデータを読み込む
SELECT `tweets`. * FROM `tweets`

#usersテーブルの全てのカラムからidが1のデータを取ってくる
SELECT `users`. * FROM `users` WHERE `users`.`id`=1
#ここが余分↓↓
SELECT `users`. * FROM `users` WHERE `users`.`id`=1

7行目の呼び出しが余分な箇所になります。

今回は、ツイートが少ない為、SQLの発行も少ないですが、ツイートデータが溜まってくると動作が遅くなる原因になってしまいます。


解決方法


どのように解決するのかと言うと、便利なメソッドがあります。


includesメソッド

指定した関連モデルをまとめて取得し、SQLの発行回数を減らすことができます。

今回の使い方だと、アソシエーションによってuserとtweetsが1対多の関係なのでtweetsを読み込むと同時にusersも読み込むようにすることができます。

このメソッドをtweetsコントローラーに記述します。

def index
  @tweets = Tweet.includes(:user)
end

上のように記述するとn+1問題が解決します。

SQLはこのように変化します。

# tweetsテーブルの全てのツイートデータを読み込む
SELECT `tweets`. * FROM `tweets`

#usersテーブルの全てのカラムからidが1のデータを取ってくる
SELECT `users`. * FROM `users` WHERE `users`.`id`=1

先ほどよりも1文少なくなっています。

その理由は、テーブルのデータを同時に読み込んでいるからです。

SQL文をこうやって見ると「n+1問題」よりも「1+n問題」の方が私はしっくりきました。

まとめ

  • n+1問題とはSQL文を余計に発行してしまう現象のこと
  • includesメソッドで解決できる

toyohiro

製造業で働く29歳。2019年6月からドットインストールでプログラミング学習開始。webアプリケーション製作をしてみたいと思い、2020年2月からrubyとrailsを学習中。今、目指していることはテレワークができるようになること。

コメントを残す

メールアドレスが公開されることはありません。 * が付いている欄は必須項目です