2011-09-29 29 views
2

我有以下幾點:Rails validates_uniqueness_of導致了指數級的查詢。你如何優化?

class List < ActiveRecord::Base 
    accepts_nested_attributes_for :list_options, :reject_if => lambda { |a| a[:title].blank? }, :allow_destroy => true 
    validates_associated :list_options 

class ListOption < ActiveRecord::Base 
    validates_uniqueness_of :title, :scope => [:list_id] 

當沿着與可以說在控制器10列表項創建一個新的列表:

@list = List.create(params[:list].merge(:user_id => current_user.id) 

數據庫中獲取單獨查詢,以檢查每個listOption,看是否它是獨一無二的。有沒有一種方法來優化以上所以數據庫不會一直打到檢查每個項目?

謝謝

+4

看起來像n個查詢,而不是x^n?把驗證拿走並放入一個唯一的組合索引,然後在拋出異常時處理異常如何? – jimworm

+0

有趣的問題,有趣的建議。我想知道在你的列表中has_many:list_options,:uniq => true'在這種情況下會如何表現?或者,也許你可以刪除validates_associated,做一個list_options.collect&:title並自己手工檢查數據庫,比如'ListOption.where(list_id:id,title:list_options.collect&:title).exists?'。不知道,爲什麼不呢? –

+0

哦,但如何建立一個複合索引,如果列表的ID尚未設置(List.new)?也許在after_save中擠壓這個? –

回答

0

我建議你在你的數據庫中創建一個唯一索引。 首先運行script/rails generate migration unique_index以獲得遷移。它將被稱爲像DB /遷移/ 20120313180200_unique_index.rb

將這個裏面:

class UniqueIndex < ActiveRecord::Migration 
    def up 
    add_index(:list_options, [:list_id, :title], :unique => true) 
    end 

    def down 
    remove_index(:list_options, :column => [:list_id, :title]) 
    end 
end 

運行rake db:migrate在軌根。如果您的數據庫會立即檢查list_id和title的每個組合都是唯一的,那麼這將失敗。

如果現在嘗試保存副本進入數據庫將提高一個ActiveRecord::RecordNotUnique例外。要處理它,請嘗試類似於

begin 
    ListOption.save 
rescue ActiveRecord::RecordNotUnique 
    # Some handling code 
    # use $! to access the exception for further details 
end 

因爲您也可以忽略這些錯誤。

將唯一索引添加到數據庫的一個副作用是查詢是否在數據庫中存儲了某對list_id和title,是否加速了很多,所以您可能能夠在模型中留下唯一性檢查,並且仍然有更好的響應時間。