“N+1选择问题”在对象关系映射(ORM)讨论中通常被称为一个问题,我理解这与必须为对象世界中看似简单的东西进行大量数据库查询有关。
有人对这个问题有更详细的解释吗?
“N+1选择问题”在对象关系映射(ORM)讨论中通常被称为一个问题,我理解这与必须为对象世界中看似简单的东西进行大量数据库查询有关。
有人对这个问题有更详细的解释吗?
当前回答
在Phabricator文档中可以找到问题的简短解释:
N+1查询问题是一个常见的性能反模式。它看起来像这样:$cats=load_cats();foreach($cat作为$cat){$cats_hats=>load_hats_for_cat($cat);// ...}假设load_cats()的实现归结为:从猫的位置选择*。。。..和load_hats_for_cat($cat)的实现如下:SELECT*FROM hat WHERE catID=。。。..当代码执行时,您将发出“N+1”个查询,其中N是猫的数量:从猫的位置选择*。。。SELECT*FROM hat WHERE catID=1SELECT*FROM hat WHERE catID=2SELECT*FROM hat WHERE catID=3从帽子中选择*,其中catID=4...
解决方案:
发出一个返回100个结果的查询比发出发出100个查询,每个查询返回1个结果。
在迭代之前加载所有数据。
其他回答
假设你有公司和雇员。公司有许多雇员(即雇员有一个字段COMPANY_ID)。
在某些O/R配置中,当您有一个映射的Company对象并访问其Employee对象时,O/R工具将为每个员工执行一次选择,如果您只是在直接SQL中执行操作,则可以从Company_id=XX的员工中选择*。因此,N(员工人数)加1(公司)
这就是EJB实体bean的初始版本是如何工作的。我相信像Hibernate这样的东西已经解决了这个问题,但我不太确定。大多数工具通常包含有关其映射策略的信息。
我不能直接评论其他答案,因为我没有足够的声誉。但值得注意的是,这个问题本质上只会出现,因为从历史上看,很多dbm在处理连接时都非常糟糕(MySQL是一个特别值得注意的例子)。因此,n+1通常比join快得多。然后有一些方法可以改进n+1,但仍然不需要连接,这就是最初的问题所在。
然而,在连接方面,MySQL现在比过去好了很多。当我第一次学习MySQL时,我经常使用联接。然后我发现它们有多慢,并在代码中改用n+1。但是,最近,我又回到了连接,因为MySQL现在在处理它们方面比我刚开始使用它时要好得多。
现在,从性能角度来看,在一组索引正确的表上进行简单联接很少有问题。如果它确实影响了性能,那么使用索引提示通常可以解决这些问题。
MySQL的一个开发团队在这里讨论了这一点:
http://jorgenloland.blogspot.co.uk/2013/02/dbt-3-q3-6-x-performance-in-mysql-5610.html
所以总结是:如果您过去一直在避免连接,因为MySQL的性能糟糕,那么请在最新版本上重试。你可能会感到惊喜。
提供的链接有一个非常简单的n+1问题示例。如果你将它应用于Hibernate,它基本上是在谈论相同的事情。查询对象时,实体将被加载,但任何关联(除非另有配置)都将被延迟加载。因此,一个查询用于根对象,另一个查询加载每个根对象的关联。返回的100个对象意味着一个初始查询,然后是100个附加查询,以获得每个对象的关联,n+1。
http://pramatr.com/2009/02/05/sql-n-1-selects-explained/
N+1选择问题是一个难题,在单元测试中检测这种情况是有意义的。我已经开发了一个小型库,用于验证给定测试方法或任意代码块执行的查询数量-JDBC Sniffer
只需在测试类中添加一个特殊的JUnit规则,并在测试方法上放置带有预期查询数的注释:
@Rule
public final QueryCounter queryCounter = new QueryCounter();
@Expectation(atMost = 3)
@Test
public void testInvokingDatabase() {
// your JDBC or JPA code
}
以Matt Solnit为例,假设您将Car和Wheels之间的关联定义为LAZY,并且需要一些Wheels字段。这意味着在第一次选择后,休眠将为每辆车执行“select*from Wheels where car_id=:id”。
这使得每个N辆车的第一次选择和更多的1次选择,这就是为什么它被称为N+1问题的原因。
为了避免这种情况,请使关联获取变得急切,以便hibernate使用连接加载数据。
但请注意,如果您多次无法访问关联的Wheels,最好将其保持为LAZY或使用Criteria更改获取类型。