了解 ORM 的嵌套 select

Understanding nested select for ORM

我正在阅读 the article 解释嵌套循环连接算法,但我并不完全理解嵌套选择的实际工作原理。这是文章提供的示例:

The examples search for employees whose last name starts with 'WIN' and fetches all SALES for these employees.

代表嵌套循环连接的查询是这些:

select employees0_.subsidiary_id as subsidiary1_0_
       -- MORE COLUMNS
from employees employees0_ 
where upper(employees0_.last_name) like ?;

select sales0_.subsidiary_id as subsidiary4_0_1_
         -- MORE COLUMNS
from sales sales0_
where sales0_.subsidiary_id=? 
  and sales0_.employee_id=?;

select sales0_.subsidiary_id as subsidiary4_0_1_
         -- MORE COLUMNS
from sales sales0_
where sales0_.subsidiary_id=? 
  and sales0_.employee_id=?;

如您所见,最后两个查询完全相同。这就是我所困惑的。为什么仅仅生成前两个查询还不够?为什么一定要生成第三个?

请记住,您粘贴的代码是 所引用的文章示例 – 一种反模式。

也就是说,查询是参数化的,因此实际上并不相同。每个查询中的两个初始 ? 字符是参数,在 for 循环的每次迭代中将被 subsidiary_id 的不同值替换。

不需要生成第三个查询。如果您手动编写 SQL 查询,您可以将所有检索到的员工的所有销售额加载为单个查询。但是当文章中的程序代码如下所示时,"N+1 query" 反模式就出现了:

for (Employees e: emp) {
  // process Employee
  for (Sales s: e.getSales()) {
    // process sale for Employee
  }
}

在该代码中,e.getSales() 方法为一名员工加载数据。此方法也没有足够的信息来加载所有其他员工的销售数据,因为 ORM 没有需要加载销售数据的完整员工列表。因此,ORM 被迫在单独的查询中加载每个员工的销售数据。

一些 ORM 可以自动避免 "N+1 query" 问题。例如,在 PonyORM 中(写在 Python 中)文章中的代码将如下所示:

# the first query loads all necessary employees
employees = select(e for e in Employee if e.lastName.startswith('WIN'))

for e in employees:
    # process Employee
    for sale in e.sales:
        # process sale for Employee

当程序开始循环查询员工时,PonyORM 会立即加载所有必要的员工。当请求第一个员工的销售项目时,PonyORM 仅为该员工加载它(因为 ORM 不知道我们的意图并假设我们可能只需要第一个员工的销售数据)。但是当请求第二个员工的销售数据时,PonyORM 注意到 "N+1 query" 反模式,看到我们在内存中加载了 N 个员工对象,并在单个查询中加载所有剩余员工的销售数据。这种行为可以被认为是启发式的。如果我们的 for-loop 包含 break 操作,它可能会加载一些额外的销售对象。但通常这种启发式方法会带来更好的性能,因为它可以大大减少查询的数量。通常加载一些额外的数据不是问题,减少往返服务器的次数更为重要。