协慌网

登录 贡献 社区

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

“N + 1 选择问题” 通常被称为对象关系映射(ORM)讨论中的一个问题,我理解它必须为对象中看起来很简单的事情做出大量的数据库查询。世界。

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

答案

假设您有一个Car对象(数据库行)的集合,并且每个Car都有一个Wheel对象(也是行)的集合。换句话说, Car - > Wheel是 1 对多的关系。

现在,假设您需要遍历所有车辆,并为每个车辆打印出车轮列表。天真的 O / R 实现将执行以下操作:

SELECT * FROM Cars;

然后Car

SELECT * FROM Wheel WHERE CarId = ?

换句话说,您有一个选择汽车,然后 N 个额外选择,其中 N 是汽车总数。

或者,可以获得所有轮子并在内存中执行查找:

SELECT * FROM Wheel

这减少了从 N + 1 到 2 的数据库往返次数。大多数 ORM 工具为您提供了几种防止 N + 1 选择的方法。

参考: Java Persistence with Hibernate ,第 13 章。

SELECT 
table1.*
, table2.*
INNER JOIN table2 ON table2.SomeFkId = table1.SomeId

这会得到一个结果集,其中 table2 中的子行通过返回 table2 中每个子行的 table1 结果而导致重复。 O / R 映射器应根据唯一键字段区分 table1 实例,然后使用所有 table2 列填充子实例。

SELECT table1.*

SELECT table2.* WHERE SomeFkId = #

N + 1 是第一个查询填充主对象的位置,第二个查询填充返回的每个唯一主对象的所有子对象。

考虑:

class House
{
    int Id { get; set; }
    string Address { get; set; }
    Person[] Inhabitants { get; set; }
}

class Person
{
    string Name { get; set; }
    int HouseId { get; set; }
}

和具有类似结构的表格。地址 “22 Valley St” 的单个查询可能会返回:

Id Address      Name HouseId
1  22 Valley St Dave 1
1  22 Valley St John 1
1  22 Valley St Mike 1

O / RM 应该填充 ID = 1,Address =“22 Valley St” 的 Home 实例,然后用 Dave,John 和 Mike 的 People 实例填充 Inhabitants 数组,只需一个查询。

对上面使用的相同地址的 N + 1 查询将导致:

Id Address
1  22 Valley St

用一个单独的查询

SELECT * FROM Person WHERE HouseId = 1

并产生一个单独的数据集

Name    HouseId
Dave    1
John    1
Mike    1

并且最终结果与单个查询的上述相同。

单一选择的优点是您可以预先获得所有数据,这可能是您最终想要的。 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”(默认)

  • 用于在 Product 上查询的获取模式是 Select

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

  • 缓存第一次没有发挥作用

  • 访问供应商

获取模式为 Select Fetch(默认)

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