:selectで取得するカラムを絞ったらパフォーマンスが倍に

最近管理しているDBサーバで継続的にスロークエリが出るようになったので、チューニングしてみたら気持ちの良い結果が出た。
結論から言うとカラム数が多いテーブルに対しては:selectで取得するカラムを絞るのがかなり有効かと思う。

現状把握

今回スロークエリの発生していたテーブルの状況を整理したのが以下。

  • レコード件数は110万件くらい
  • カラム数は30程度
  • インデックスは効いている(explainで確認済み)
  • 処理の性質的にキャッシュは使えない

スロークエリになっているのはもっぱら以下のクエリ。

select * from pages order by updated_at limit 100;

Railsのコードで見るとこんなかんじ。

Page.all(:order => 'updated_at', :limit => 100)

こんな単純なクエリが実行に2秒から10秒程度もかかってスロークエリとして記録されているのは切ない。
インデックスは効いているので問題解決には他のアプローチが必要になる。

考えるに対象は30以上カラムがあってレコードサイズもそこそこ大きいテーブル。
そこで取得するカラムを絞って余計なカラムを取得しないようにしてみたらどうかと思った。

というかクエリが単純すぎてまずはそれくらいしか浮かばなかったわけだけど。

ベンチマークとチューニング

計測なくしてチューニングなしということでベンチマークで使ったのはmybench。

ベンチマークとチューニングは手元の開発環境で実行した。
こちらレコード件数は3万件程度。本番環境より大幅に少ないが十分だろう。たぶん。

全カラム取得とカラムを絞った結果の比較が以下。
10クライアントから100回ずつ、計1000回のリクエストを送るというのを試行回数3回ずつ行った結果。
serialは経過時間(秒)です。

まずは全部まるごと取得している現状のクエリ。

select * from pages order by updated_at limit 100;
# Page.all(:order => 'updated_at', :limit => 100)
  serial  : 29.173278
  serial  : 29.433684
  serial  : 30.258237

これを取得カラムを絞ったものにしてみると。

select id, updated_at from pages order by updated_at limit 100;
# Page.all(:select => 'id, updated_at', :order => 'updated_at', :limit => 100)

  serial  : 16.422306
  serial  : 17.562543
  serial  : 16.070013

倍近く速くなった。
うつくしい。

いやぁ、チューニングって本当に気持ちがいいものですね。
これを本番環境にアップしたらスロークエリもパッタリなくなり幸せになれました。

以下詳しく見たい人向け
select * from pages order by updated_at limit 100;
# Page.all(:order => 'updated_at', :limit => 100)

test: 1000 0.001631 0.060372 0.029173278 29.173278 342.779443571614
  clients : 10
  queries : 1000
  fastest : 0.001631
  slowest : 0.060372
  average : 0.029173278
  serial  : 29.173278
  q/sec   : 342.779443571614

test: 1000 0.001535 0.06981 0.029433684 29.433684 339.746801657584
  clients : 10
  queries : 1000
  fastest : 0.001535
  slowest : 0.06981
  average : 0.029433684
  serial  : 29.433684
  q/sec   : 339.746801657584

test: 1000 0.00298 0.065291 0.030258237 30.258237 330.488521191767
  clients : 10
  queries : 1000
  fastest : 0.00298
  slowest : 0.065291
  average : 0.030258237
  serial  : 30.258237
  q/sec   : 330.488521191767

select id, updated_at from pages order by updated_at limit 100;
# Page.all(:select => 'id, updated_at', :order => 'updated_at', :limit => 100)

test: 1000 0.000327 0.037233 0.016422306 16.422306 608.927881382797
  clients : 10
  queries : 1000
  fastest : 0.000327
  slowest : 0.037233
  average : 0.016422306
  serial  : 16.422306
  q/sec   : 608.927881382797

test: 1000 0.001182 0.050836 0.017562543 17.562543 569.393623691057
  clients : 10
  queries : 1000
  fastest : 0.001182
  slowest : 0.050836
  average : 0.017562543
  serial  : 17.562543
  q/sec   : 569.393623691057

test: 1000 0.000301 0.04706 0.016070013 16.070013 622.277032383234
  clients : 10
  queries : 1000
  fastest : 0.000301
  slowest : 0.04706
  average : 0.016070013
  serial  : 16.070013
  q/sec   : 622.277032383234