前書いた書きかけのメモを発見したので加筆して載せてみます。ちなみにこの話はMysql(InnoDB)利用時限定です。
モデルのcountメソッドは
SELECT count(*) AS count_all FROM `blogs`
のようなSQL文を発行します。このようなSQL文では、基本的に主キーインデックスによる全索引検索が行われます。通常、インデックスだけを読み込む全索引検索のほうが、テーブルだけを読み込む全表検索よりもI/O回数が少なくなるため高速になりますが、InnoDBの主キーインデックスは他の列値と直結している仕様で(この場合は)余計な列値を読み込むことになるため、あまり高速になりません。主キー以外のインデックスを利用した方が高速になるようです。
実際に試してみた
rails2.3.2(たぶん), mysql5.0.77で試しました。
適当なrailsプロジェクトを作成し、scaffoldで適当なblogを作ります
script/generate scaffold blog title:string body:text
titleカラムにインデックスを追加します。
class CreateBlogs < ActiveRecord::Migration def self.up create_table :blogs do |t| t.string :title t.text :body t.timestamps end add_index "blogs", "title" # 追加 end def self.down drop_table :blogs end end
config/database.ymlを書いてから下記のコマンドでDBとテーブルを作ります。
rake db:create rake db:migrate
その後、初期データを入れるためのscript/load_dataを作ります。titleには30文字、bodyには1000文字のランダムなアルファベットを100000個入れてみました。
def random_text(num) text = "" source = ("a".."z").to_a + ("A".."Z").to_a num.times do text << source[rand(source.size)] end text end 1.upto(100000) do title = random_text(30) body = random_text(1000) Blog.create(:title => title, :body => body) end
script/load_dataを実行してランダムなデータを入れます。
script/runner script/load_data
これで準備完了。script/consoleで、countとcount_by_sqlの違いを試してみます。
script/console
>> Blog.count SQL (862.4ms) SELECT count(*) AS count_all FROM `blogs` => 100000 >> Blog.count_by_sql("select count(*) from blogs force index(index_blogs_on_title)") Blog Count (75.7ms) select count(*) from blogs force index(index_blogs_on_title) => 100000 >>
- countのみは862.4ms
- count_by_sqlは75.7ms
10倍以上の差がつきました。
まとめ
大量のデータ件数をcountで調べている場合は、count_by_sqlに切り替えるとパフォーマンスがよくなるかもしれません。