読者です 読者をやめる 読者になる 読者になる

おもしろwebサービス開発日記

Ruby や Rails を中心に、web技術について書いています

countよりもcount_by_sqlの方がいいかも

前書いた書きかけのメモを発見したので加筆して載せてみます。ちなみにこの話は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に切り替えるとパフォーマンスがよくなるかもしれません。

参考

現場で使える MySQL (DB Magazine SELECTION)
松信 嘉範
翔泳社
売り上げランキング: 12878
おすすめ度の平均: 5.0
5 よいです。
5 ODBC接続など実用的な内容
5 実践的な良書、作者の今後にも期待
4 システム管理者向け