DataMapperのcountが遅い

約4万件ほどデータが格納されているデータベースに対してcountを呼ぶと数秒もかかるので、調べてみました。

require 'dm-core'
require 'dm-migrations'
require 'benchmark'

class Test
  include DataMapper::Resource
  property :id,			Serial
  property :number,		Integer
end


DataMapper.setup(:default, 'sqlite:test.db')
DataMapper.finalize
DataMapper.auto_upgrade!

# Testには4万件のデータが格納されている
Benchmark.bm do |x|
 x.report{ Test.count }
end

実行結果:

      user     system      total        real
  5.975000   0.000000   5.975000 ( 6.030345)

速度を測ると約6秒ほどかかっています。これだと使い物にならないのでどんなSQLが吐き出されているのか調べてみました。

...

DataMapper::Logger.new($stdout, :debug)  # SQLを調べるためにLoggerを加える
DataMapper.setup(:default, 'sqlite:test.db')
DataMapper.finalize
DataMapper.auto_upgrade!

...

実行結果:

  SELECT "id", "number" FROM "tests" ORDER BY "id"

COUNT関数が発行されてると思ったら、すべてのデータを取得してからカウントしているので遅いことがわかります。内部的には配列の数を返しています。

ここでなぜCOUNT関数を使わないのかを調べてみたら、「dm-aggregates」をrequireする必要がありました。

require 'dm-core'
require 'dm-aggregates'  # 追加
require 'dm-migrations'
require 'benchmark'

...

実行結果:

      user     system      total        real
  0.016000   0.000000   0.016000 ( 0.004000)
  SELECT COUNT(*) FROM "tests"

これでCOUNT関数が発行され高速に処理するようになりました。


DataMapperをrequireするときはこのようなミスをしないために、「data_mapper」をrequireするようにしたほうが無難です。data_mapperをrequireすると一度に必要なモジュールをrequireしてくれます。

require 'rubygems'
require 'data_mapper'
  • dm-core
  • dm-aggregates
  • dm-constraints
  • dm-migrations
  • dm-transactions
  • dm-serializer
  • dm-timestamps
  • dm-validations
  • dm-types