“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=?
结果:
这是 N + 1 选择问题!