LoginSignup
1
1

More than 3 years have passed since last update.

Railsのcountメソッドは必要なときだけ使おう

Last updated at Posted at 2020-12-05

結論

countメソッド はその都度SQLクエリを実行する。
N+1問題となるため、不要なときはsizeメソッドを使用する。

sizeメソッド is 何

以下のActive Record公式GitHubで定義を確認します。
https://github.com/rails/rails/blob/94b5cd3a20edadd6f6b8cf0bdf1a4d4919df86cb/activerecord/lib/active_record/relation.rb#L210

def size
  loaded? ? @records.length : count(:all)
end

未ロード時にはcountされて、
キャッシュがあるときはレコードのlengthを取得しているだけのようです。

countメソッドとの比較

投稿のコメント数を取得することを例にとって説明します。

countメソッドを使用するとき

<%= micropost.commented_users.count %>

このとき実行されるSQLは以下になります。

SELECT COUNT(*) FROM `users` INNER JOIN `comments` ON `users`.`id` = `comments`.`user_id` WHERE `comments`.`post_id` = 4

SELECT COUNT(*) FROM `users` INNER JOIN `comments` ON `users`.`id` = `comments`.`user_id` WHERE `comments`.`post_id` = 3

SELECT COUNT(*) FROM `users` INNER JOIN `comments` ON `users`.`id` = `comments`.`user_id` WHERE `comments`.`post_id` = 2

SELECT COUNT(*) FROM `users` INNER JOIN `comments` ON `users`.`id` = `comments`.`user_id` WHERE `comments`.`post_id` = 1

「ユーザーID」と、「コメントのユーザーID」が一致している投稿を全て取得しています。
コメントテーブルに対して何度もアクセスしているため、N+1問題が発生中。

sizeメソッドを使用するとき

- <%= micropost.commented_users.count %>
+ <%= micropost.commented_users.size %>

この状態でログを見ると先程のSQLは実行されていません。

未ロード時にはCOUNTを実行し、
キャッシュがあれば実行しないのがsizeメソッドの挙動です。

最後に

N+1問題は自動で検出してくれるbulletのようなgemもありますが、
自分のコードにどんな無駄があるのか考えると勉強になります。

特にActive Recordは便利で何が起きているのか深く考えてこなかったので
今回調べてみてこのように記事にしました。

参考

https://scoutapm.com/blog/three-activerecord-mistakes-ja

https://pikawaka.com/rails/includes#N%20+%201%E5%95%8F%E9%A1%8C%E3%81%A8%E3%81%AF%EF%BC%9F

https://github.com/rails/rails/blob/94b5cd3a20edadd6f6b8cf0bdf1a4d4919df86cb/activerecord/lib/active_record/relation.rb#L210

1
1
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
1
1