はあ…… 13 万か。高いなあ。
まあ、一応 15 万用意してたから想定の範囲内ではあるんだけど、できれば 10 万くらいで済んでほしかったよ。 つーか、それくらいで済んだらちょっと無駄遣いしてみようと思ってた (具体的には某 touch とか) んだけど、夢は破れたなー。
もう買ってから 7 年経つし、雪国はどうしても下回りの劣化が激しいので (主に融雪剤の影響)、しかたないっちゃーしかたないんだが。 かといって、買い替えるような余裕もしばらく無さそうだし、無理に安く上げるよりはちゃんと金かけて整備してもらった方が良いだろう。
Ruby は知ってるけど、MySQL とかその他のデータベースアプリはほとんど使ったことなんて無い…… という人 (それはわし) が、いきなり Rails で何かを作り始めたときにやってしまいがちな失敗。 それが、
というやつ。 わしは今のところ MySQL しか使ったことが無いので、とりあえず以下は MySQL だけを例にして書くが、
という事実がある。 MySQL を使っている人には自明のことなのかも知れないが、そうじゃない素人にとっては、それはわりと想定の範囲外だったりする。 というか、明示的に作成しないかぎり、プライマリキー以外にインデックスが存在しないということが、そもそもわからない。
そうするとどうなるかというと、簡単なサンプルを作っているうちは良いのだが、10 万、100 万といった数のレコードを持つテーブルを使う段になって破綻する。 まさかそんな数のレコードを、Hoge.find :all で全部取得するようなアホはいないはずで、当然何かしら :conditions オプションでもってふるいをかけるだろう。 だが、その :conditions にプライマリキー (Rails において、それは id と決まっている) 以外を使っていれば (もちろん当然使わざるを得ないんだが) ものすごく検索が遅くなる。 ともあれ、
である。 ちなみにこれは migration ファイル (db/migrate 以下) で、
add_index テーブル名, カラム名
とすることで追加できる。 例えば
% ./script/generator model person name:string age:integer
てな感じで作ったモデルであれば、Person というクラスと people というテーブルができているはずなので、ここで作られた migrate ファイルでも良いし、新たに作ったものでも良いから self.up に
add_index :people, :name
と書けば良い。 ちなみに self.down には、
remove_index :people, :name
と書いておく。
あと、MySQL の場合は複数のカラムをキーに検索する場合には最初に使われるカラムのインデックスしか使われないという制限があって、そこを何とかするために複合インデックスという仕組みが存在している。 これは要するに、上のようなモデルで、
Person.find :all, :conditions => ['name = ? AND age = ?', 'hoge', 10]
みたいな条件を指定したときには、例え age カラムにインデックスが存在してもそれは使われないということで、こういう場合に age でもインデックスが使われるようにしたいなら複合インデックスを用意しなさいということ (まあ、上記のようなシチュエーションなら、name の条件で十分小さい数まで絞り込めそうなんで、あまり気にすることはないだろうけど)。 複合インデックスは以下のように作れる。
add_index :people, [:name, :age]
この場合 remove_index は書き方がちょっと変わるので注意。
remove_index :people, :column => [:name, :age]
ともあれ、複合インデックスについては MySQL のマニュアルをよく読んだ方が良い。 あと、上記のやり方で作成するときには指定した各カラム名から、インデックスの名前が自動的に決定されるんだが、それがあまりに長すぎると MySQL 側でエラーになる場合がある。 そんなときは自分でインデックス名を指定してやると回避できる。
add_index :people, [:name, :age], :name => 'index_hoge'
この場合の remove_index は以下。
remove_index :people, :column => [:name, :age], :name => 'index_hoge'
さて、データベース側の話はとりあえずこれくらいにして、Ruby としての話も少し。
わりとよく言われる Ruby におけるパフォーマンスチューニング法に、
というのがあると思う。 これは要するにメモリの使用量を最小限にしろ… というのとほぼ同義だと思うが、ActiveRecord というやつは結構富豪的な作りというか、何も考えないで find すると SQL 的には SELECT * FROM ... で全部のカラムを持ってきてそれをきっちりインスタンスに詰めてよこすようになっていて、これが積み重なると結構な無駄になっていると思われる。
これはデータベース側にとっても結構無駄が多くて、例えば name というカラムが必要なだけであれば、当然のように SELECT name FROM ... という SQL 文を書きなさいという話になるはず。 で、ActiveRecord でこれをやるには :select オプションを使う。
Person.find :all, :select => 'id,name', :conditions => 'age > 10'
とか書くと、age が 10 より大きいレコードが、id と name カラムだけを持ってインスタンス化される。 別に :select のところは 'name' でも良いけど、大抵の場合 id も無いと困るので、入れておいた方が無難。 あと、この :select オプションは find だけじゃなく最終的に find と同じようにクエリが発行されるものであれば大抵は使えるので、例えば has_many や belongs_to の指定時などにも使える。
has_many :friends, :select => 'id,name'
みたいな感じで。
インデックスの話もそうだけど、この :select に関しては入門記事なんかにはあまり書いていないことが多くて、ある程度 Rails に慣れてからハマる人ってそれなりにいるんじゃないかなーと思ったりした。 あと、自分で SQL を書くならちゃんと必要なカラムだけ SELECT するのに、ActiveRecord でもそれができることに気付かずに放置してる、なんて人もいるかも。
あと余談。
script/server をフォアグラウンドで動かしつつ、別の端末で script/console を動かせば、find などが発行するクエリがどんな風になっているかリアルタイムに確認できて便利。 has_many なんかが、ちゃんとクエリの発行を遅延させているところなんかも確認できる。 例えば、
class Hoge has_many :fugas end
みたいなモデルがあったとして、
>> hoge = Hoge.find :first >> hoge.fugas; nil
とか、
>> hoge.fugas.find :first; nil
みたいなことをやってみると良い。 けっして hoge.fugas の時点で SELECT * FROM fugas WHERE (fugas.id = 1) みたいな富豪的なクエリが発行されているなんてことは無いのがわかるはず。 ちなみに、いちいち ; nil などと付けているのは、そうしないと irb が値を表示させるためにせっかく遅延されてるクエリが発行されてしまうから。
何はともあれ、Rails でちゃんとしたアプリケーションを作りたいなら、結局データベースに関する知識は必要だよ、という話かな。
車検取って2か月して、車がだめになりましたです。。
それはツラい。
わしは車検取って一ヶ月で、衝動的に買い替えたってことはありましたが(苦笑