Resource.find(id).child_resources
這樣的寫法很直覺,也很方便,但是會造成兩個query:
Resource Load (x ms) SELECT "resource".* FROM "resource" where id = n
ChildResource Load (x ms) SELECT "child_resource".* FROM "child_resource" WHERE "child_resource"."resource_id" = n
如果改成這樣的寫法只需要一次的query:
ChildResources.where("resource_id=#{id}")
雖然只少了一次的query,但最多能減少50%的sql操作時間
Resource.all (沒有加select)
當你可能只是很單純的要Resource的id及name,而不需要肥肥的"content"欄位,使用select只拉出需要的欄位,可以幫以省下不少傳輸時間和記憶體。
雖然可以使用slim_scrooge這種gem,不過別這麼懶,自己養成好習慣吧!
利用joins將需要的資料一次拿完
剛開始開發rails的時候,常常會拿出model後就直接往view丟,相關的relation到時候再拿就好,例如這樣:<%= @category.products.counts %>但是這樣常常會產生無謂的sql query,少量還好,當你在前台對一堆@catgories跑each的時候會發生很可怕的情形....也就是所謂的 "N+1 Query"
所以盡量養成一個習慣,就是要用到的資料一次拉進來就好,最嚴謹的檢查方式就是將產出的資料用attributes轉成hash,所有要拿的資料都在裡面了,沒有query進來的relation絕對拿不到。不過這樣的作法可能有點矯枉過正就是XD
要一次拿完所需資料最簡單就是用join+select操作,例如:
@products=Category.joins(:products=>:photo).select("categories.name cat_name, products.name pro_name, photos.url") <% @products.each do |product| %> <%= product.cat_name %> <%= product.pro_name %> <%= image_tag product.url %> <% end %>而不是
@products=Product.all <% @products.each do |product| %> <%= product.category.name %> <%= product.name %> <%= image_tag product.photo.url %> <% end %>
適時使用counter_cache
相信這種情境一定很常見:進入網站後,螢幕顯示出你有幾個朋友、幾個相簿、幾篇部落格
這時,在controller裡面....
Bad code:
@friend_count = current_user.friends.count @album_count = current_user.albums.count @blog_count = current_user.blogs.count
這時我們該使用counter cache把他cache起來:
在users增加friends_count、albums_count、blogs_count等欄位
在friend.rb、album.rb、blog.rb中的
belongs_to :user改成
belongs_to :user, :counter_cache => true如果之前已經有資料了,記得把user.xxx_count給update到目前的count數,但為了保護counter的正確性,counter是read-only的,我們只能使用Base.reset_counter來設定他。
而counter的reset程式碼最好是寫在migration.rb裡,讓各種環境下migrate時順便reset counter:
class AddCounterCacheToUsers < ActiveRecord::Migration
def self.up
add_column :users, :comments_count, :integer, :default => 0
User.find_each do |user|
User.reset_counter user.id, :friends, :albums, :blogs
end
end
def self.down
remove_column :users, :comments_count
end
end
Good code:
@counts = User.find(current_user.id).select(:friends_count, :albums_count, :blogs_count).attributes
善用Transaction
利用transaction
find.each