区别在于是否重新发起select count,以及只发起select count还是select *

count

无论是否加载,count都会重新发起select count

单表时

[48] pry(main)> hus = Human.where('id < ?', 10);
[49] pry(main)> binding.trace_tree(htmp: 'rails/single_count'){hus.count}
   (25.7ms)  SELECT COUNT(*) FROM "humen" WHERE (id < 10)
=> 2
[50] pry(main)> hus
  Human Load (0.1ms)  SELECT "humen".* FROM "humen" WHERE (id < 10)
=> [#<human:0x007fbac86a3a08 id:="" 3,="" name:="" "jack",="" created_at:="" sun,="" 30="" jul="" 2017="" 15:14:01="" utc="" +00:00,="" updated_at:="" sun,="" 30="" jul="" 2017="" 15:14:01="" utc="" +00:00="">,
 #<human:0x007fbac86a37b0 id:="" 6,="" name:="" "ken",="" created_at:="" tue,="" 01="" aug="" 2017="" 13:30:47="" utc="" +00:00,="" updated_at:="" tue,="" 01="" aug="" 2017="" 13:30:47="" utc="" +00:00="">]
[51] pry(main)> binding.trace_tree(htmp: 'rails/single_count_load'){hus.count}
   (25.3ms)  SELECT COUNT(*) FROM "humen" WHERE (id < 10)
=> 2</human:0x007fbac86a37b0></human:0x007fbac86a3a08>


未加载的调用栈

20170801_214249_140_single_count.html

已加载的调用栈

20170801_214305_116_single_count_load.html

关联时

[6] pry(main)> hu = Human.first
  Human Load (0.1ms)  SELECT  "humen".* FROM "humen" ORDER BY "humen"."id" ASC LIMIT ?  [["LIMIT", 1]]
=> #<human:0x007fbac7a210a0 id:="" 3,="" name:="" "jack",="" created_at:="" sun,="" 30="" jul="" 2017="" 15:14:01="" utc="" +00:00,="" updated_at:="" sun,="" 30="" jul="" 2017="" 15:14:01="" utc="" +00:00="">
[7] pry(main)> binding.trace_tree(htmp: 'rails/count'){hu.cells.count}
   (33.7ms)  SELECT COUNT(*) FROM "cells" WHERE "cells"."human_id" = ?  [["human_id", 3]]
=> 2
[8] pry(main)> hu = Human.first;hu.cells
  Human Load (0.1ms)  SELECT  "humen".* FROM "humen" ORDER BY "humen"."id" ASC LIMIT ?  [["LIMIT", 1]]
  Cell Load (0.1ms)  SELECT "cells".* FROM "cells" WHERE "cells"."human_id" = ?  [["human_id", 3]]
=> [#<cell:0x007fbac8b20590 id:="" 2,="" human_id:="" 3,="" created_at:="" sun,="" 30="" jul="" 2017="" 15:14:21="" utc="" +00:00,="" updated_at:="" sun,="" 30="" jul="" 2017="" 15:14:21="" utc="" +00:00="">,
 #<cell:0x007fbac8b20068 id:="" 3,="" human_id:="" 3,="" created_at:="" sun,="" 30="" jul="" 2017="" 15:14:24="" utc="" +00:00,="" updated_at:="" sun,="" 30="" jul="" 2017="" 15:14:24="" utc="" +00:00="">]
[9] pry(main)> binding.trace_tree(htmp: 'rails/count_loaded'){hu.cells.count}
   (35.4ms)  SELECT COUNT(*) FROM "cells" WHERE "cells"."human_id" = ?  [["human_id", 3]]
=> 2</cell:0x007fbac8b20068></cell:0x007fbac8b20590></human:0x007fbac7a210a0>


未加载的调用栈

20170801_205008_333_count.html

已加载的调用栈

20170801_205420_618_count_loaded.html

size

对于size,未加载时发起select count,已加载时直接获取集合的大小

单表时

[52] pry(main)> hus = Human.where('id < ?', 10);
[53] pry(main)> binding.trace_tree(htmp: 'rails/single_size'){hus.size}
   (28.7ms)  SELECT COUNT(*) FROM "humen" WHERE (id < 10)
=> 2
[54] pry(main)> hus
  Human Load (0.2ms)  SELECT "humen".* FROM "humen" WHERE (id < 10)
=> [#<human:0x007fbac8718128 id:="" 3,="" name:="" "jack",="" created_at:="" sun,="" 30="" jul="" 2017="" 15:14:01="" utc="" +00:00,="" updated_at:="" sun,="" 30="" jul="" 2017="" 15:14:01="" utc="" +00:00="">,
 #<human:0x007fbac8703f20 id:="" 6,="" name:="" "ken",="" created_at:="" tue,="" 01="" aug="" 2017="" 13:30:47="" utc="" +00:00,="" updated_at:="" tue,="" 01="" aug="" 2017="" 13:30:47="" utc="" +00:00="">]
[55] pry(main)> binding.trace_tree(htmp: 'rails/single_size_load'){hus.size}
=> 2</human:0x007fbac8703f20></human:0x007fbac8718128>


未加载的调用栈

20170801_214402_859_single_size.html

已加载的调用栈

20170801_214415_476_single_size_load.html

源码如下

def size
  loaded? ? @records.length : count(:all)
end


关联时

[10] pry(main)> hu = Human.first
  Human Load (0.1ms)  SELECT  "humen".* FROM "humen" ORDER BY "humen"."id" ASC LIMIT ?  [["LIMIT", 1]]
=> #<human:0x007fbac8c0e038 id:="" 3,="" name:="" "jack",="" created_at:="" sun,="" 30="" jul="" 2017="" 15:14:01="" utc="" +00:00,="" updated_at:="" sun,="" 30="" jul="" 2017="" 15:14:01="" utc="" +00:00="">
[11] pry(main)> binding.trace_tree(htmp: 'rails/size'){hu.cells.size}
   (31.7ms)  SELECT COUNT(*) FROM "cells" WHERE "cells"."human_id" = ?  [["human_id", 3]]
=> 2
[12] pry(main)> hu = Human.first;hu.cells
  Human Load (0.1ms)  SELECT  "humen".* FROM "humen" ORDER BY "humen"."id" ASC LIMIT ?  [["LIMIT", 1]]
  Cell Load (0.1ms)  SELECT "cells".* FROM "cells" WHERE "cells"."human_id" = ?  [["human_id", 3]]
=> [#<cell:0x007fbac8f5c410 id:="" 2,="" human_id:="" 3,="" created_at:="" sun,="" 30="" jul="" 2017="" 15:14:21="" utc="" +00:00,="" updated_at:="" sun,="" 30="" jul="" 2017="" 15:14:21="" utc="" +00:00="">,
 #<cell:0x007fbac8f4bcf0 id:="" 3,="" human_id:="" 3,="" created_at:="" sun,="" 30="" jul="" 2017="" 15:14:24="" utc="" +00:00,="" updated_at:="" sun,="" 30="" jul="" 2017="" 15:14:24="" utc="" +00:00="">]
[13] pry(main)> binding.trace_tree(htmp: 'rails/size_loaded'){hu.cells.size}
=> 2</cell:0x007fbac8f4bcf0></cell:0x007fbac8f5c410></human:0x007fbac8c0e038>


未加载的调用栈

20170801_205510_259_size.html

已加载的调用栈

20170801_205532_722_size_loaded.html

关键源码如下

# activerecord-5.1.2/lib/active_record/associations/collection_association.rb
def size
  if !find_target? || loaded?
    target.size
  elsif !association_scope.group_values.empty?
    load_target.size
  elsif !association_scope.distinct_value && target.is_a?(Array)
    unsaved_records = target.select(&:new_record?)
    unsaved_records.size + count_records
  else
    count_records
  end
end

# activerecord-5.1.2/lib/active_record/associations/association.rb
def find_target?
  !loaded? && (!owner.new_record? || foreign_key_present?) && klass
end


length

而length,未加载时发起select *,然后获取集合大小;已加载时直接获取集合的大小

单表时

[56] pry(main)> hus = Human.where('id < ?', 10);
[57] pry(main)> binding.trace_tree(htmp: 'rails/single_length'){hus.length}
  Human Load (32.5ms)  SELECT "humen".* FROM "humen" WHERE (id < 10)
=> 2
[58] pry(main)> hus
=> [#<human:0x007fbac7a89588 id:="" 3,="" name:="" "jack",="" created_at:="" sun,="" 30="" jul="" 2017="" 15:14:01="" utc="" +00:00,="" updated_at:="" sun,="" 30="" jul="" 2017="" 15:14:01="" utc="" +00:00="">,
 #<human:0x007fbac90a0240 id:="" 6,="" name:="" "ken",="" created_at:="" tue,="" 01="" aug="" 2017="" 13:30:47="" utc="" +00:00,="" updated_at:="" tue,="" 01="" aug="" 2017="" 13:30:47="" utc="" +00:00="">]
[59] pry(main)> binding.trace_tree(htmp: 'rails/single_length_load'){hus.length}
=> 2</human:0x007fbac90a0240></human:0x007fbac7a89588>


未加载的调用栈

20170801_214459_259_single_length.html

已加载的调用栈

20170801_214531_947_single_length_load.html

关键源码如下

# activerecord-5.1.2/lib/active_record/relation/delegation.rb
delegate :to_xml, :encode_with, :length, :each, :uniq, :to_ary, :join,
         :[], :&, :|, :+, :-, :sample, :reverse, :compact, :in_groups, :in_groups_of,
         :to_sentence, :to_formatted_s, :as_json,
         :shuffle, :split, :index, to: :records

# activerecord-5.1.2/lib/active_record/relation.rb
def records
  load
  @records
end

def load(&block)
  exec_queries(&block) unless loaded?
  self
end


关联时

[14] pry(main)> hu = Human.first
  Human Load (0.1ms)  SELECT  "humen".* FROM "humen" ORDER BY "humen"."id" ASC LIMIT ?  [["LIMIT", 1]]
=> #<human:0x007fbac8912fc8 id:="" 3,="" name:="" "jack",="" created_at:="" sun,="" 30="" jul="" 2017="" 15:14:01="" utc="" +00:00,="" updated_at:="" sun,="" 30="" jul="" 2017="" 15:14:01="" utc="" +00:00="">
[15] pry(main)> binding.trace_tree(htmp: 'rails/length'){hu.cells.length}
  Cell Load (31.3ms)  SELECT "cells".* FROM "cells" WHERE "cells"."human_id" = ?  [["human_id", 3]]
=> 2
[16] pry(main)> hu = Human.first;hu.cells
  Human Load (0.1ms)  SELECT  "humen".* FROM "humen" ORDER BY "humen"."id" ASC LIMIT ?  [["LIMIT", 1]]
  Cell Load (0.1ms)  SELECT "cells".* FROM "cells" WHERE "cells"."human_id" = ?  [["human_id", 3]]
=> [#<cell:0x007fbac9061ea0 id:="" 2,="" human_id:="" 3,="" created_at:="" sun,="" 30="" jul="" 2017="" 15:14:21="" utc="" +00:00,="" updated_at:="" sun,="" 30="" jul="" 2017="" 15:14:21="" utc="" +00:00="">,
 #<cell:0x007fbac9061950 id:="" 3,="" human_id:="" 3,="" created_at:="" sun,="" 30="" jul="" 2017="" 15:14:24="" utc="" +00:00,="" updated_at:="" sun,="" 30="" jul="" 2017="" 15:14:24="" utc="" +00:00="">]
[17] pry(main)> binding.trace_tree(htmp: 'rails/length_loaded'){hu.cells.length}
=> 2</cell:0x007fbac9061950></cell:0x007fbac9061ea0></human:0x007fbac8912fc8>


未加载的调用栈

20170801_205619_905_length.html

已加载的调用栈

20170801_205742_640_length_loaded.html

关键源码如下

# activerecord-5.1.2/lib/active_record/relation/delegation.rb
delegate :to_xml, :encode_with, :length, :each, :uniq, :to_ary, :join,
         :[], :&, :|, :+, :-, :sample, :reverse, :compact, :in_groups, :in_groups_of,
         :to_sentence, :to_formatted_s, :as_json,
         :shuffle, :split, :index, to: :records

# activerecord-5.1.2/lib/active_record/associations/collection_proxy.rb
def records
  load_target
end

# activerecord-5.1.2/lib/active_record/associations/collection_association.rb
def load_target
  if find_target?
    @target = merge_target_lists(find_target, target)
  end

  loaded!
  target
end