Member 具有到此地址的关系,并由宏 has_one :address, :as => :addressable, :dependent => :destroy 定义。注意当 ActiveRecord 加载了 Member 时,您并不会看到地址字段。但如果在控制台中键入 member.address,就可以在 development.log 中看到清单 4 中的内容:
清单 4. 访问关系会强制数据库访问
^[[36;2m./vendor/plugins/paginating_find/lib/paginating_find.rb:98:in `find'^[[0m ^[[4;35;1mAddress Load (0.252084)^[[0m ^[[0mSELECT * FROM addresses WHERE (addresses.addressable_id = 1 AND addresses.addressable_type = 'Member') LIMIT 1^[[0m ^[[35;2m./vendor/plugins/paginating_find/lib/paginating_find.rb:98:in `find'^[[0m
所以 ActiveRecord 并不会为地址关系执行查询,直到您实际访问 member.address。通常,这种懒散设计会工作得很好,因为持久性框架无需移动如此多的数据来加载成员。但如果您想要访问很多成员以及所有成员的地址,如清单 5 所示:
清单 5. 用地址检索多个成员
Member.find([1,2,3]).each {|member| puts member.address.city}
由于您应该看到针对每个地址的查询,所以就性能而言,结果并不尽如人意。清单 6 给出了问题的全部:
清单 6. N+1 问题的查询
^[[4;36;1mMember Load (0.004063)^[[0m ^[[0;1mSELECT * FROM members WHERE (members.`id` IN (1,2,3)) ^[[0m ^[[36;2m./vendor/plugins/paginating_find/lib/paginating_find.rb:98:in `find'^[[0m ^[[4;35;1mAddress Load (0.000989)^[[0m ^[[0mSELECT * FROM addresses WHERE (addresses.addressable_id = 1 AND addresses.addressable_type = 'Member') LIMIT 1^[[0m ^[[35;2m./vendor/plugins/paginating_find/lib/paginating_find.rb:98:in `find'^[[0m ^[[4;36;1mAddress Columns (0.073840)^[[0m ^[[0;1mSHOW FIELDS FROM addresses^[[0m ^[[4;35;1mAddress Load (0.002012)^[[0m ^[[0mSELECT * FROM addresses WHERE (addresses.addressable_id = 2 AND addresses.addressable_type = 'Member') LIMIT 1^[[0m ^[[35;2m./vendor/plugins/paginating_find/lib/paginating_find.rb:98:in `find'^[[0m ^[[4;36;1mAddress Load (0.000792)^[[0m ^[[0;1mSELECT * FROM addresses WHERE (addresses.addressable_id = 3 AND addresses.addressable_type = 'Member') LIMIT 1^[[0m ^[[36;2m./vendor/plugins/paginating_find/lib/paginating_find.rb:98:in `find'^[[0m
结果正如我所预见的那样糟糕。所有成员共用一个查询,而每个地址各用一个查询。我们检索了三个成员,所以一共用了四个查询。如果是 N 个成员,就会有 N+1 个查询。这就是可怕的 N+1 问题。大多数持久性框架都采用热关联(eager association)来解决该问题。Rails 也不例外。如果需要访问关系,就可以选择将其包括到初始查询中。ActiveRecord 使用 :include 选项来实现此目的。如果将查询更改为 Member.find([1,2,3], :include => :address).each {|member| puts member.address.city},结果就会稍好一些:
清单 7. 解决 N+1 问题










