哪个是避免n + 1问题最快的表现方式?为什么?

Kei*_*ith 5 sql optimization orm select-n-plus-1

我想添加一些实用程序方法来帮助避免遗留应用程序中的许多n + 1问题.

常见的模式是这样的:

select a.* /* over 10 columns */
from [table-A] a
where /* something */
Run Code Online (Sandbox Code Playgroud)

检索到ClassA记录实例的集合

然后,子实例是惰性检索的:

select b.* /* over 10 columns */
from [sub-table-B] b
where b.ParentId = @ClassA_ID
Run Code Online (Sandbox Code Playgroud)

这导致n + 1选择问题.大多数情况下,这不是一个主要问题,因为ClassA在不经常命中的页面上只检索了几个实例,但是在越来越多的地方,这个n + 1问题导致页面变得太慢,因为应用程序已经缩放.

我正在寻找替换此应用程序的现有数据访问代码的一部分,以便一起检索ClassA实例和ClassB实例.

我认为有三种方法可以做到:

1)ClassA像我们现在一样获取实例,然后ClassB在一个聚合调用中获取实例:

select b.*
from [sub-table-B] b
where b.ParentId in ( /* list of parent IDs */ )
Run Code Online (Sandbox Code Playgroud)

这是两个单独的DB调用,动态SQL的查询计划将不可缓存(由于ID列表).

2)ClassB使用子查询获取实例:

select b.*
from [sub-table-B] b
    inner join [table-A] a
        on b.ParentId = a.[ID]
where /* something */
Run Code Online (Sandbox Code Playgroud)

这也是两个DB调用,并且[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 */
Run Code Online (Sandbox Code Playgroud)

这只是一个DB调用,但现在我们得到[table-A]重复的内容- 结果集将更大,从DB向客户端发送数据的时间将更多.

所以这真的是3个可能的妥协:

  1. 2个DB调用,没有查询缓存
  2. 2个DB调用,复杂查询评估两次
  3. 1个DB调用,结果集明显更大

我可以为任何一个父子对表测试这三种模式,但我有很多这些模式.我想知道的是哪种模式一直更快?更重要的是为什么?这些妥协中的一个是明显的性能杀手吗?

Linq,EF和NHibernate等现有机制使用什么?

有没有第四种方式比3种更好?

Jan*_*Jan 1

我认为 EF 和 L2S 使用第三种方法 - 肯定只有一个数据库调用。

通常,更多的数据库往返比具有更大结果集的更少的数据库往返花费更多的时间。

也许存在一些边缘情况,表 A 中有大量数据,并且较大的结果集会过多地增加向客户端的传输时间。

但这主要是数据库服务器和客户端之间的延迟和带宽问题。

第四种方法可能是编写一个返回多个结果集的存储过程。一个用于您查询的每个表,仅包含您需要的记录。这适合您的第一种方法,但减少到一次往返。但这会使事情变得有点复杂,并且不像其他方法那么灵活。