Railsを使用していると、データベースからのレコード件数の取得方法に迷うことがよくあります。特に、count, size, lengthという3つのメソッドが存在するため、どれを選択すれば最適なのか疑問に思うことがあると思います。
この記事では、そんな疑問を解消するために、以下の内容を詳しく解説します。
- count, size, lengthのそれぞれの特性と使用シーン
- 各メソッドの内部での動作の違い
- 最適なシチュエーションでのメソッドの選択方法
Active RecordはRailsの強力な機能の一つで、データベース操作を直感的に行うことができます。しかし、その中でも件数取得のメソッドは微妙な違いがあり、その選択はパフォーマンスや効率に大きく影響します。
この記事を通して3つのメソッドの違いを正確に理解し、日々の開発での最適な選択ができるようになりましょう。
countは必ずクエリを発行する
今回の記事ではRails Consoleを活用して、Usersテーブルのレコードを全件取得した後に各メソッドを実行したらどの様に処理されるかを見ていきます。
Rails Consoleはプロジェクトルートでbin/rails cを実行する事で利用が可能です。
まずはcountメソッドの挙動です。
irb(main):001:0> users = User.all #usersテーブルのレコードを全件取得
irb(main):002:0> users.count #取得したレコードの件数を取得
(0.6ms) SELECT COUNT(*) FROM `users`
=> 3
irb(main):003:0> users.count #取得したレコードの件数を再度取得
(1.9ms) SELECT COUNT(*) FROM `users`
=> 3
ここで注目して欲しいのはcountメソッドを実行すると毎回データベースに対してクエリを投げているという点です。
SELECT COUNT(*) FROM `users`
eachなどのループ処理の中でcountを実行する際にな十分注意しましょう。
- countは実行時に必ずデータベースに対してクエリを発行する
- 毎回クエリを発行するため必ず最新のレコードの件数を取得出来る
lengthはメモリに情報をロード(キャッシュ)する
irb(main):001:0> users = User.all #usersテーブルのレコードを全件取得
irb(main):002:0> users.length #取得したレコードの件数を取得
User Load (0.3ms) SELECT `users`.* FROM `users`
=> 3
irb(main):003:0> users.length #取得したレコードの件数を再度取得
=> 3
lengthは1つめのcountと違い実行時にメモリに情報を保存します。
そのため、2回目以降のlengthではSELECT users.* FROM usersが実行(表示)されていない点に注目しましょう。
- 初回のlength実行時にデータをメモリに保存している
- 一度データをロードした後は何度lengthを実行しても新たなクエリは発行されない
sizeはlengthとcountのあわせ技
irb(main):001:0> users = User.all #usersテーブルのレコードを全件取得
irb(main):002:0> users.size #取得したレコードの件数を取得
(0.6ms) SELECT COUNT(*) FROM `users`
=> 3
irb(main):003:0> users.size #取得したレコードの件数を再度取得
(0.7ms) SELECT COUNT(*) FROM `users`
=> 3
sizeはデータをallで取得した後に実行するとcountと同様に必ずクエリを発行します。
しかし下記の様に間にlengthを挟みメモリ上にデータを乗せておくと、sizeもlengthと同様にメモリから取得するようになりクエリを発行しなくなります。
irb(main):001:0> users = User.all
irb(main):002:0> users.length
User Load (0.3ms) SELECT `users`.* FROM `users`
=> 3
irb(main):003:0> users.size
=> 3
- メモリにデータが無い場合はcountと同じくクエリを発行する
- メモリにデータがある場合はlengthと同じくクエリを発行せずに件数を取得可能
loadメソッドでメモリにデータを格納する
lengthを使用するとデータはメモリ上にキャッシュされ、クエリの再発行が不要になります。しかし、lengthを用いてデータをメモリにキャッシュした後にcountを使用するのは少し違和感があるかと思います。
このような状況では、loadメソッドを使用してデータをメモリ上にロードし、その後sizeを使うという方法があります。
irb(main):001:0> users = User.all
irb(main):002:0> users.load
User Load (0.3ms) SELECT `users`.* FROM `users`
=> #, #, #]>
irb(main):003:0> users.size
=> 3
irb(main):004:0> users.size
=> 3
loadを使った後はlengthの時と同様に、sizeで件数を取得してもクエリが発行されないようになります。
件数を取得するだけならメソッドチェーン
irb(main):001:0> count = User.all.count
(0.7ms) SELECT COUNT(*) FROM `users`
=> 3
今までのコードでは一度usersという変数にallの結果を入れてからcountなどの件数を取得するメソッドを実行していました。
しかしレコードの各カラム情報が不要で件数だけが必要な場合は、メソッドチェーンを使うことでよりシンプルに書けます。
件数を取得する場合はどのメソッドを使うべきか
基本的にはlengthとcountの合せ技のsizeを使うのが安定ですが、1つだけ注意点があります。
それはデータがメモリ上に乗っている(キャッシュされている)場合は最新の件数を取得出来ない点です。
irb(main):001:0> users = User.all.load #usersテーブルのレコードを全件取得しキャッシュする
User Load (0.3ms) SELECT `users`.* FROM `users`
irb(main):002:0> users.size #キャッシュの情報を元に件数を取得するのでクエリは発行されない
=> 3
irb(main):003:0> User.create(id: 4) #1件ユーザーのレコードを追加する
(0.2ms) BEGIN
User Create (0.3ms) INSERT INTO `users` (`id`, `created_at`, `updated_at`) VALUES (4, '2021-01-16 14:56:19.564822', '2021-01-16 14:56:19.564822')
(1.1ms) COMMIT
=> #
irb(main):004:0> users.size #usersの情報はキャッシュされているので件数は3件のまま
=> 3
irb(main):005:0> users.count #実際は1件レコードを作成したので4件存在する
(0.6ms) SELECT COUNT(*) FROM `users`
=> 4
countは必ずクエリを発行するため、処理の途中でデータがインサート(作成)された場合でも常に最新の件数を取得することが出来ます。
一方sizeはデータがキャッシュされている場合は、クエリを発行せずにキャッシュから件数を取得するために必ずしも正確な件数を取得出来ない場合があります。
何も考えずにsizeメソッドを使った場合にはこの様な罠があるという事を覚えておきましょう。
メソッドは理解した上で使うべき
似たような処理を行うメソッドでも内部の処理は大きく異なります。
具体的にどのような処理が行われているかを意識しその時の最適なメソッドを選択する事で、パフォーマンスを意識したより良い実装を出来るようになりましょう。
- Ruby初心者に向けた学習ロードマップ!挫折しないための学習方法!
- 【初心者向け】MacでRailsを使えるようにするための環境構築方法を徹底解説!
- 【初心者向け】RailsでMySQLを使うための手順をコマンド付きで解説!
- Railsが難しい理由を現役エンジニアが解説!学習効率を上げるには○○を高めるべき!
- Rails初心者に向けたコマンドまとめ!newやgenerateコマンド、簡単に使える便利関数を紹介!
- Railsでルーティングを作成!ネストやパラメーターを取得する方法も解説!
- Railsの部分テンプレートの書き方をコード付きで解説!引数の渡し方やディレクトリ名の悩みとはおさらば!
- Railsでリンクヘルパー(link_to)を使ってサクッとリンクを生成する方法を解説!