yujiroのプログラミング

勉強内容をアウトプットし、サボらないようにする為のブログ

<DAY79> n + 1 問題について

\ Follow me!! /

f:id:yujiro0320:20190415152250p:plain

学習状況

●学習日数 79日 

●学習時間(本日) 10時間

●累計学習時間 267.0時間

●一日あたりの平均学習時間 3.38時間 

n + 1 問題とは

データを呼び出す際に大量のSQLが発行されてしまう問題。 モデルを利用してデータベースの情報にアクセスする際に、SQLが発行される。 SQLが発行されるたびにデータベースに対して通信が走るので、SQLが大量に発行されれば処理が重くなる。

includesメソッドを使って解決

railsで用意されている、データーベースにアクセスするためのメソッドです。allとか一緒のくくりです。 指定したモデルを結合するよって意味。

@tweets = Tweet.includes(:hoge)  結合したいモデル名

例を出してみる

f:id:yujiro0320:20190511144738p:plain

A君は、あるツイートに対して5件コメントしている。(自分のツイートに5回もコメントしているのが気になるけど) テーブル名は、usersが正しいです。

コードの流れを整理して考えると、

URLのデータが飛ぶ。
rotesの定義に従い対応するコントローラが動く。
コントローラの指示に基づき、モデルが動く。
モデルが指示を翻訳しデータベースにアクセスする。
モデルが取得したデーターをコントローラに返す。
対応するビューを開く。
ビューで記載されているrubyのコードが動く

発生した箇所について

対応するtweetsコントローラのshowアクションには、

@tweet = Tweet.find(params[:id])
@comments = @tweet.comment

対応するレコードのツイート情報取得し、コメントテーブルにあるすべてのデーターを加えて、変数@commentsに格納しています。

次にビューを見て見ます。

<% @comments.each do |comment| %>
<p>
<%= comment.user.nickname %>
<%= comment.text%>
</p>
<%end%>

each文を使って取り出しています。 モデルから持ってきた情報は配列形式になっているんです。 アソシーエションを使って、usersテーブルに入っているnicknameを取得しています。 モデルクラスはuserだけど、テーブルはusersです。 userクラスから情報を取得する時に、n+1問題が発生しています。

eachで定義されたcomment変数には、usersテーブルの情報はありません。
結合しないと、5件のコメントに対してuser_idを取得する動作を繰り返す事になります。
includes(:user)とする事で、@tweet.commentと結合します。
よって、SQLの発行は1回だけで済むわけです。

解決

コントローラーの部分だけ見ると、なぜuserモデルと結合するのかわからないですが、ビューデータやりとりを把握すれば理由が明確になりました。

@tweet = Tweet.find(params[:id])
@comments = @tweet.comment.include(:user)