5

我希望添加一些实用方法来帮助避免遗留应用程序中的许多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 种可能的妥协:

  1. 2 次数据库调用,无查询缓存
  2. 2 次数据库调用,复杂查询评估了两次
  3. 1 次数据库调用,显着更大的结果集

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

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

有没有比所有 3 更好的第 4 种方法?

4

3 回答 3

1

我认为 EF 和 L2S 使用您的第三种方法 - 肯定只有一个 db 调用。

通常,更多的 db 往返需要更多的时间,而不是更少的 db 往返和更大的结果集。

也许在某些极端情况下,您在表 A 中有大量数据,并且较大的结果集过多地增加了向客户端的传输时间。

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

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

于 2011-09-23T08:36:39.053 回答
0

大多数现代数据库(如果您使用参数化查询,肯定是 Oracle)将缓存查询评估,您几乎不会遇到它们。

Django 的一些 ORM将允许您创建自定义查询并仅返回渲染页面所需的部分结果。这是一个很好的方法——如果你看到一个数据库热点优化它,否则让 ORM 去做它的投标。

请记住,无论您的财务经理怎么说,硬件都很便宜(顾问两天的工作成本与服务器升级的成本相同)。

于 2011-09-23T08:53:44.460 回答
0

在我看来,“这是最快的方式”取决于您的数据库服务器的延迟和带宽,以及您的结果集有多大。

在延迟是瓶颈(ADSL 网络?)的情况下,如果您的结果集不是很大,您最好向您的服务器发送一个查询。由于 [table-A] 记录被多次发送,因此使用的带宽会更大,但从全球范围来看,这可能是将数据发送到客户端的最快方式。

于 2011-09-23T08:52:21.313 回答