什么是ORM(对象关系映射)中的“ N + 1选择问题”?

倾然丶 夕夏残阳落幕 提交于 2019-12-09 11:17:16

在对象关系映射(ORM)讨论中,通常将“ N + 1选择问题”表示为问题,并且我了解到它与必须对对象中看起来很简单的内容进行大量数据库查询有关。世界。

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


#1楼

查看有关主题的Ayende帖子: 打击NHibernate中的Select N + 1问题

基本上,当使用诸如NHibernate或EntityFramework之类的ORM时,如果您具有一对多(主从细节)关系,并且想要列出每个主记录的所有详细信息,则必须对N数据库中,“ N”表示主记录的数量:1个查询以获取所有主记录,N个查询,每个主记录一个,以获取每个主记录的所有详细信息。

更多的数据库查询调用→更多的延迟时间→降低了应用程序/数据库性能。

但是,ORM具有避免此问题的选项,主要是使用JOIN。


#2楼

正如其他人更优雅地指出的那样,问题是您要么拥有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列,你只是做一个SELECT上的ID表INNER JOIN荷兰国际集团与子表WHERE batch_id= (反之亦然)。 您只需要确保按id列排序即可,因为这将使合并结果列更加容易(否则,整个结果集都需要HashMap / Table,这可能还不错。)

然后,您只需定期清理id表。

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

现在,您要执行的查询数是由OneToMany列数决定的。


#3楼

一位百万富翁拥有N辆汽车。 您想要所有(4)个轮子。

一(1)个查询会加载所有汽车,但是对于每(N)个汽车都会提交一个单独的查询以加载车轮。

费用:

假设索引适合ram。

1 + N查询解析和计划+索引搜索,以及1 + N +(N * 4)个用于装载有效载荷的板访问。

假设索引不适合ram。

在最坏的情况下,额外的成本为1 + N个板块以获取装载指数。

摘要

瓶颈是板的访问(在硬盘上每秒约有70次随机访问)急切的联接选择还将对板进行1 + N +(N * 4)次有效负载访问。 因此,如果索引适合ram-没问题,它的速度足够快,因为仅涉及ram操作。


#4楼

以Matt Solnit为例,假设您将Car和Wheels之间的关联定义为LAZY,并且需要一些Wheels字段。 这意味着在第一次选择之后,休眠将为每辆汽车执行“从* where car_id =:id的车轮中选择*”。

这使得每N辆汽车都具有第一选择权和更多选择权,这就是为什么它被称为n + 1问题。

为避免这种情况,请尽快获取关联,以便休眠状态通过联接加载数据。

但是请注意,如果很多次您都没有访问关联的Wheels,最好将其保持为LAZY或使用Criteria更改获取类型。


#5楼

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

***** 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”(默认)

  • 用于查询产品的提取模式为“选择”

  • 提取模式(默认):访问供应商信息

  • 缓存第一次不起作用

  • 供应商被访问

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

// 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选择的问题!

易学教程内所有资源均来自网络或用户发布的内容,如有违反法律规定的内容欢迎反馈
该文章没有解决你所遇到的问题?点击提问,说说你的问题,让更多的人一起探讨吧!