我正在阅读the article解释嵌套循环连接算法,但我并不完全理解嵌套选择的实际工作原理。以下是文章提供的示例:
示例搜索姓氏以“WIN”开头的员工 并为这些员工提取所有销售额。
表示嵌套循环连接的查询是:
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=?;
如您所见,最后两个查询完全相同。这就是我所困惑的。为什么不只是生成前两个查询不够?为什么我们必须生成第三个?
答案 0 :(得分:1)
请记住,您粘贴的代码是不要做的参考文章示例 - 反模式。
也就是说,查询是参数化的,因此实际上并不相同。每个查询中的两个初始?
字符在for循环的每次迭代中都会被subsidiary_id
的不同值替换。
答案 1 :(得分:0)
没有必要生成第三个查询。如果手动编写SQL查询,则可以将所有检索到的员工的所有销售作为单个查询加载。但是当程序代码在文章中看起来像是“N + 1查询”反模式时出现:
for (Employees e: emp) {
// process Employee
for (Sales s: e.getSales()) {
// process sale for Employee
}
}
在该代码中,e.getSales()
方法为一名员工加载数据。此方法也没有足够的信息来加载所有其他员工的销售数据,因为ORM没有必要加载销售数据的完整员工列表。因此,ORM被迫在单独的查询中加载每个员工的销售数据。
某些ORM可以自动避免“N + 1查询”问题。例如,在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查询”反模式,看到我们在内存中加载了N个员工对象,并在单个查询中加载了所有剩余员工的销售额。此行为可以视为启发式。如果我们的for
- 循环包含break
操作,它可能会加载一些额外的销售对象。但通常这种启发式方法可以提高性能,因为它可以大大减少查询次数。通常,加载一些额外数据不是问题,减少到服务器的往返次数就更为重要。