2020 年 09 月 === # 蒼時 先從 Rails API 文件裡面找到 [`#includes`](https://api.rubyonrails.org/classes/ActiveRecord/QueryMethods.html#method-i-includes) 方法,在 `QueryMethods` 模組裡面也可以看到像是 `#eager_load` 和 `#preload` 兩個方法,這也是 Rails 最常被拿來比較的三個實作。 ## #includes 方法 展開的原始碼會轉到 `#spawn` 後的物件處理 ```ruby def includes(*args) check_if_method_has_arguments!(:includes, args) spawn.includes!(*args) end ``` 繼續在 GitHub 尋找,會發現 `#spawn` 基本上是複製一次物件,因此實際上要找的 `#includes!` 實際上就在 `#includes` 原始碼下方( https://github.com/rails/rails/blob/070d4afacd3e9721b7e3a4634e4d026b5fa2c32c/activerecord/lib/active_record/relation/query_methods.rb#L151 ) ```ruby def includes!(*args) # :nodoc: args.reject!(&:blank?) args.flatten! self.includes_values |= args self end ``` 關鍵點應該是對 `includes_values` 做了交集處理 ```ruby Relation::VALUE_METHODS.each do |name| method_name = \ case name when *Relation::MULTI_VALUE_METHODS then "#{name}_values" when *Relation::SINGLE_VALUE_METHODS then "#{name}_value" when *Relation::CLAUSE_METHODS then "#{name}_clause" end class_eval <<-CODE, __FILE__, __LINE__ + 1 def #{method_name} # def includes_values default = DEFAULT_VALUES[:#{name}] # default = DEFAULT_VALUES[:includes] @values.fetch(:#{name}, default) # @values.fetch(:includes, default) end # end def #{method_name}=(value) # def includes_values=(value) assert_mutability! # assert_mutability! @values[:#{name}] = value # @values[:includes] = value end # end CODE end ``` 不過線索到 `includes_values` 就斷掉了,他是利用 DSL 生成的方法,而這個方法會把資料把存在 `@values` 裡面。 ## self.includes_values 相關的程式碼 在 `Relation` 物件上會做一些處理( https://github.com/rails/rails/blob/d04acc0bc5b5b9f5b512460ebb149ea8ac3012f3/activerecord/lib/active_record/relation.rb ) > 記憶中 Relation 物件上會被製作成像是 `User_Relation` 的物件,通常是在 Model 被定義的時候 ### eager_loading? 判斷是否需要 `eager_load` 的情況,如果有 `eager_load` 或者 `includes_value` 且符合條件 ```ruby # Returns true if relation needs eager loading. def eager_loading? @should_eager_load ||= eager_load_values.any? || includes_values.any? && (joined_includes_values.any? || references_eager_loaded_tables?) end ``` > 因為 `||` 優先度比 `&&` 低,所以 `includes_values` 存在時還要符合後面兩個條件 * `joined_includes_values` 的條件是 `joins` 和 `includes` 的資料表有重疊 * `references_eager_loaded_tables?` 的條件複雜很多,大致上就是把 `references` 過的表去掉 `joins` 的表如果沒有其他數值的話就符合(待驗證) ### preload_associations 另一個情況是處理 `preload` 時要找出關聯的資料 ```ruby def preload_associations(records) # :nodoc: preload = preload_values preload += includes_values unless eager_loading? preloader = nil scope = strict_loading_value ? StrictLoadingScope : nil preload.each do |associations| preloader ||= build_preloader preloader.preload records, associations, scope end end ``` 這邊明顯有一個判定條件就是如果被 `eager_load` 過的畫就不會加入到 `preload` 的條件,因此兩者實際上是互斥的 ## eager_load 文件上的說明如下 > Forces eager loading by performing a LEFT OUTER JOIN on args 簡單說如果使用 `eager_load` 的話會強制的做 `LEFT OUTER JOIN` 的處理 ## preload 文件上說明如下 > Allows preloading of args, in the same way that includes does 看起來是做跟 `includes` 類似的處理,基本上也就是先把相關的表做 `SELECT` 出來的處理 ## 小結 由上面幾點來看,基本上 `includes` 在預設情況下跟 `preload` 是相同的,但是當我們的 `includes` 符合 `joins` 或者 `references` 的處理,因為需要做 `LEFT OUTER JOIN` 的處理,他就會被切換到 `eager_load` 的處理下面,同時 `preload` 的處理則跳過所有 `includes` 相關的處理。 簡單來說只要符合 `eage_load` 的條件,就會因為需要做 JOIN 查詢而無法進行 `preload` 類型的處理,使用 `joins` 或者 `references` 都會造成同樣的現象(即使手動做 `preload` 也不會有反應) > `preload` 只要不跟 `joins` 或 `references` 重疊都會正常運作,至於當重疊時是如何被排除在 `preload` 之外可能就要另外討論,或者 `preload` 本身也會有 `JOIN` 類型的處理,目前還沒有追到原因。
×
Sign in
Email
Password
Forgot password
or
By clicking below, you agree to our
terms of service
.
Sign in via Facebook
Sign in via Twitter
Sign in via GitHub
Sign in via Dropbox
Sign in with Wallet
Wallet (
)
Connect another wallet
New to HackMD?
Sign up