我希望添加一些实用方法来帮助避免遗留应用程序中的许多n+1 问题。
常见的模式是这样的:
select a.* /* over 10 columns */
from [table-A] a
where /* something */
检索到ClassA
记录实例的集合
然后子实例被惰性检索:
select b.* /* over 10 columns */
from [sub-table-B] b
where b.ParentId = @ClassA_ID
这会导致 n+1 选择问题。大多数情况下,这不是一个主要问题,因为ClassA
在不经常点击的页面上只检索了几个实例,但在越来越多的地方,这个 n+1 问题会导致页面在应用程序扩展时变得太慢。
我正在寻找替换此应用程序的现有数据访问代码的一部分,以便一起检索ClassA
实例和实例。ClassB
我认为有3种方法可以做到这一点:
1)ClassA
像我们现在一样获取实例,然后ClassB
在一个聚合调用中获取实例:
select b.*
from [sub-table-B] b
where b.ParentId in ( /* list of parent IDs */ )
这是两个单独的 DB 调用,动态 SQL 的查询计划将不可缓存(由于 ID 列表)。
2)ClassB
使用子查询获取实例:
select b.*
from [sub-table-B] b
inner join [table-A] a
on b.ParentId = a.[ID]
where /* something */
这也是两个数据库调用,并且必须对查询[table-A]
进行两次评估。
3) 将所有实例放在一起并删除重复数据ClassA
:
select a.*, b.*
from [table-A] a
left outer join [sub-table-B] b
on a.[ID] = b.ParentId
where /* something */
这只是一个 DB 调用,但现在我们得到了[table-A]
重复的内容——结果集会更大,将数据从 DB 发送到客户端的时间会更长。
所以实际上这是 3 种可能的妥协:
- 2 次数据库调用,无查询缓存
- 2 次数据库调用,复杂查询评估了两次
- 1 次数据库调用,显着更大的结果集
我可以为任何一对父子表测试这三种模式,但我有很多。我想知道的是哪种模式始终更快?更重要的是为什么?这些妥协之一是明显的性能杀手吗?
Linq、EF 和 NHibernate 等现有机制使用什么?
有没有比所有 3 更好的第 4 种方法?