ActiveRecord的lazy loading与eager loading

看来大家还对ActiveRecord的lazy loading和eager loading不是很清楚

ActiveRecord默认是lazy loading的,而加上:include选项后可以指定eager loading一些字段
:include  - specify second-order associations that should be eager loaded when the collection is loaded.

看例子:
Post.find(:all, :include => :comments)
  # => SELECT ... FROM posts LEFT OUTER JOIN comments ON ...


我再把active_recordassociations.rb的源代码翻出来给大家看看:
1,base.rb
  def with_scope(method_scoping = {}, action = :merge, &block)
    method_scoping = method_scoping.method_scoping if method_scoping.respond_to?(:method_scoping)

    # Dup first and second level of hash (method and params).
    method_scoping = method_scoping.inject({}) do |hash, (method, params)|
      hash[method] = (params == true) ? params : params.dup
      hash
    end

    method_scoping.assert_valid_keys([ :find, :create ])

    if f = method_scoping[:find]
      f.assert_valid_keys([ :conditions, :joins, :select, :include, :from, :offset, :limit, :order, :readonly, :lock ])
      f[:readonly] = true if !f[:joins].blank? && !f.has_key?(:readonly)
    end

    # Merge scopings
    if action == :merge && current_scoped_methods
      method_scoping = current_scoped_methods.inject(method_scoping) do |hash, (method, params)|
        case hash[method]
          when Hash
            if method == :find
              (hash[method].keys + params.keys).uniq.each do |key|
                merge = hash[method][key] && params[key] # merge if both scopes have the same key
                if key == :conditions && merge
                  hash[method][key] = [params[key], hash[method][key]].collect{ |sql| "( %s )" % sanitize_sql(sql) }.join(" AND ")
                elsif key == :include && merge
                  hash[method][key] = merge_includes(hash[method][key], params[key]).uniq
                else
                  hash[method][key] = hash[method][key] || params[key]
                end
              end
            else
              hash[method] = params.merge(hash[method])
            end
          else
            hash[method] = params
        end
        hash
      end
    end

    self.scoped_methods << method_scoping

    begin
      yield
    ensure
      self.scoped_methods.pop
    end
  end

  def find_every(options)
    records = scoped?(:find, :include) || options[:include] ?
      find_with_associations(options) : 
      find_by_sql(construct_finder_sql(options))

    records.each { |record| record.readonly! } if options[:readonly]

    records
  end


2,associations.rb
module ActiveRecord
  module Associations
    module ClassMethods
      private
        def find_with_associations(options = {})
          catch :invalid_query do
            join_dependency = JoinDependency.new(self, merge_includes(scope(:find, :include), options[:include]), options[:joins])
            rows = select_all_rows(options, join_dependency)
            return join_dependency.instantiate(rows)
          end
          []
        end

        def select_all_rows(options, join_dependency)
          connection.select_all(
            construct_finder_sql_with_included_associations(options, join_dependency),
            "#{name} Load Including Associations"
          )
        end

        def construct_finder_sql_with_included_associations(options, join_dependency)
          scope = scope(:find)
          sql = "SELECT #{column_aliases(join_dependency)} FROM #{(scope && scope[:from]) || options[:from] || table_name} "
          sql << join_dependency.join_associations.collect{|join| join.association_join }.join
 
          add_joins!(sql, options, scope)
          add_conditions!(sql, options[:conditions], scope)
          add_limited_ids_condition!(sql, options, join_dependency) if !using_limitable_reflections?(join_dependency.reflections) && ((scope && scope[:limit]) || options[:limit])

          sql << "GROUP BY #{options[:group]} " if options[:group]
 
          add_order!(sql, options[:order], scope)
          add_limit!(sql, options, scope) if using_limitable_reflections?(join_dependency.reflections)
          add_lock!(sql, options, scope)
 
          return sanitize_sql(sql)
        end

        class JoinDependency

          class JoinAssociation < JoinBase
            def association_join
              join = case reflection.macro
                when :has_and_belongs_to_many
                  " LEFT OUTER JOIN %s ON %s.%s = %s.%s " % [
                     table_alias_for(options[:join_table], aliased_join_table_name),
                     aliased_join_table_name,
                     options[:foreign_key] || reflection.active_record.to_s.classify.foreign_key,
                     parent.aliased_table_name, reflection.active_record.primary_key] +
                  " LEFT OUTER JOIN %s ON %s.%s = %s.%s " % [
                     table_name_and_alias, aliased_table_name, klass.primary_key,
                     aliased_join_table_name, options[:association_foreign_key] || klass.table_name.classify.foreign_key
                     ]
                when :has_many, :has_one
                    ...
                when :belongs_to
                    ...
                else
                    ...
              join
            end
          end

        end

调用流程是这样的:
ActiveRecord.find* -> with_scope -> find_every -> find_with_associations -> select_all_rows -> construct_finder_sql_with_included_associations -> association_join
如果提供:include选项,则调用find_with_associations,即eager loading,否则调用find_by_sql,为lazy loading
最后我们看到JoinAssociation的association_join方法使用LEFT OUTER JOIN来做关联查询,做eager loading

一切都弄清楚了吧?

BTW:对于open source项目,遇到问题时看看source code是很好的solution

你可能感兴趣的:(sql,F#,ActiveRecord)