“N+1选择问题”在对象关系映射(ORM)讨论中通常被称为一个问题,我理解这与必须为对象世界中看似简单的东西进行大量数据库查询有关。

有人对这个问题有更详细的解释吗?


当前回答

假设你有公司和雇员。公司有许多雇员(即雇员有一个字段COMPANY_ID)。

在某些O/R配置中,当您有一个映射的Company对象并访问其Employee对象时,O/R工具将为每个员工执行一次选择,如果您只是在直接SQL中执行操作,则可以从Company_id=XX的员工中选择*。因此,N(员工人数)加1(公司)

这就是EJB实体bean的初始版本是如何工作的。我相信像Hibernate这样的东西已经解决了这个问题,但我不太确定。大多数工具通常包含有关其映射策略的信息。

其他回答

正如其他人更优雅地指出的那样,问题是您要么拥有OneToMany列的笛卡尔积,要么正在进行N+1选择。无论是可能的巨大结果集,还是与数据库的聊天。

我很惊讶没有提到这一点,但我是如何解决这个问题的。。。我制作了一个半临时ID表。当您有IN()条款限制时,我也会这样做。

这并不适用于所有情况(可能甚至不适用于大多数情况),但如果您有很多子对象,使得笛卡儿乘积无法控制(即大量的OneToMany列,结果的数量将是列的乘积),并且它更像是一个批处理作业,那么它就特别适用。

首先,将父对象ID作为批处理插入到ID表中。batch_id是我们在应用程序中生成并保存的东西。

INSERT INTO temp_ids 
    (product_id, batch_id)
    (SELECT p.product_id, ? 
    FROM product p ORDER BY p.product_id
    LIMIT ? OFFSET ?);

现在,对于每个OneToMany列,您只需在id表INNER上执行SELECT,然后使用WHERE batch_id=(反之亦然)将子表JOIN。您只需要确保按id列排序,因为这将使合并结果列更容易(否则,您将需要一个HashMap/Table用于整个结果集,这可能不会那么糟糕)。

然后,只需定期清理ids表。

如果用户选择例如100个左右不同的项目进行某种批量处理,这也特别有效。将100个不同的ID放入临时表中。

现在,您正在执行的查询数量是OneToMany列的数量。

在我看来,《Hibernate陷阱:为什么关系应该懒惰》中的文章与真正的N+1问题完全相反。

如果您需要正确的解释,请参阅Hibernate-第19章:提高性能-获取策略

选择提取(默认值)为极易受到N+1选择的影响问题,因此我们可能希望启用联接获取

与产品有一对多关系的供应商。一个供应商拥有(供应)许多产品。

***** Table: Supplier *****
+-----+-------------------+
| ID  |       NAME        |
+-----+-------------------+
|  1  |  Supplier Name 1  |
|  2  |  Supplier Name 2  |
|  3  |  Supplier Name 3  |
|  4  |  Supplier Name 4  |
+-----+-------------------+

***** Table: Product *****
+-----+-----------+--------------------+-------+------------+
| ID  |   NAME    |     DESCRIPTION    | PRICE | SUPPLIERID |
+-----+-----------+--------------------+-------+------------+
|1    | Product 1 | Name for Product 1 |  2.0  |     1      |
|2    | Product 2 | Name for Product 2 | 22.0  |     1      |
|3    | Product 3 | Name for Product 3 | 30.0  |     2      |
|4    | Product 4 | Name for Product 4 |  7.0  |     3      |
+-----+-----------+--------------------+-------+------------+

因素:

供应商的懒惰模式设置为“true”(默认)用于查询产品的获取模式为Select获取模式(默认):访问供应商信息缓存第一次不起作用访问供应商

提取模式为选择提取(默认)

// It takes Select fetch mode as a default
Query query = session.createQuery( "from Product p");
List list = query.list();
// Supplier is being accessed
displayProductsListWithSupplierName(results);

select ... various field names ... from PRODUCT
select ... various field names ... from SUPPLIER where SUPPLIER.id=?
select ... various field names ... from SUPPLIER where SUPPLIER.id=?
select ... various field names ... from SUPPLIER where SUPPLIER.id=?

结果:

1个产品选择语句供应商的N个选择语句

这是N+1选择问题!

提供的链接有一个非常简单的n+1问题示例。如果你将它应用于Hibernate,它基本上是在谈论相同的事情。查询对象时,实体将被加载,但任何关联(除非另有配置)都将被延迟加载。因此,一个查询用于根对象,另一个查询加载每个根对象的关联。返回的100个对象意味着一个初始查询,然后是100个附加查询,以获得每个对象的关联,n+1。

http://pramatr.com/2009/02/05/sql-n-1-selects-explained/

这是对问题的一个很好的描述

现在您了解了这个问题,通常可以通过在查询中执行连接获取来避免。这基本上强制获取延迟加载的对象,因此数据在一个查询中而不是n+1个查询中检索。希望这有帮助。