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

本是古典 何须时尚 2024-03-29 15:33 229阅读 0赞

问:

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

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

答1:

huntsbot.com – 程序员副业首选,一站式外包任务、远程工作、创意产品分享订阅平台。

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

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

  1. SELECT * FROM Cars;

然后对于每个Car:

  1. SELECT * FROM Wheel WHERE CarId = ?

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

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

  1. SELECT * FROM Wheel

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

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

为了澄清“这很糟糕” - 您可以使用 1 个选择 (SELECT * from Wheel;) 而不是 N+1 来获得所有轮子。 N 较大时,性能影响可能非常显着。

@tucuxi 我很惊讶你因为错误而获得了如此多的支持。数据库非常擅长索引,对特定 CarID 进行查询会很快返回。但是如果你得到了所有的轮子一次,你将不得不在你的应用程序中搜索 CarID,它没有被索引,这比较慢。除非您在到达数据库时遇到重大延迟问题,否则 n + 1 实际上更快 - 是的,我用大量真实世界的代码对其进行了基准测试。

@ariel“正确”的方法是获取按 CarId 排序的所有车轮(1 个选择),如果需要比 CarId 更多的详细信息,请对所有汽车进行第二次查询(总共 2 个查询)。现在打印出来是最佳的,不需要索引或辅助存储(您可以迭代结果,无需全部下载)。您对错误的事物进行了基准测试。如果您仍然对自己的基准测试充满信心,您介意发表更长的评论(或完整的答案)来解释您的实验和结果吗?

“Hibernate(我不熟悉其他 ORM 框架)为您提供了几种处理它的方法。”这些方式是什么?

@Ariel 尝试在不同的机器上使用数据库和应用程序服务器运行您的基准测试。根据我的经验,往返数据库的开销比查询本身要高。所以,是的,查询确实很快,但造成严重破坏的是往返。我已将“WHERE Id = const”转换为“WHERE Id IN (const, const, ...)”,并从中获得了数量级的增长。

答2:

huntsbot.com – 高效赚钱,自由工作

什么是 N+1 查询问题

N+1 查询问题发生在数据访问框架执行 N 个附加 SQL 语句以获取执行主 SQL 查询时可能已检索到的相同数据时。

N值越大,执行的查询越多,对性能的影响越大。而且,与可以帮助您找到运行缓慢的查询的慢查询日志不同,N+1 问题不会被发现,因为每个单独的附加查询运行速度足够快,不会触发慢查询日志。

问题在于执行大量附加查询,总体而言,这些查询需要足够的时间来减慢响应时间。

假设我们有以下 post 和 post_comments 数据库表,它们形成了一对多的表关系:

https://i.stack.imgur.com/T1uWG.png

我们将创建以下 4 个 post 行:

  1. INSERT INTO post (title, id)
  2. VALUES ('High-Performance Java Persistence - Part 1', 1)
  3. INSERT INTO post (title, id)
  4. VALUES ('High-Performance Java Persistence - Part 2', 2)
  5. INSERT INTO post (title, id)
  6. VALUES ('High-Performance Java Persistence - Part 3', 3)
  7. INSERT INTO post (title, id)
  8. VALUES ('High-Performance Java Persistence - Part 4', 4)

而且,我们还将创建 4 个 post_comment 子记录:

  1. INSERT INTO post_comment (post_id, review, id)
  2. VALUES (1, 'Excellent book to understand Java Persistence', 1)
  3. INSERT INTO post_comment (post_id, review, id)
  4. VALUES (2, 'Must-read for Java developers', 2)
  5. INSERT INTO post_comment (post_id, review, id)
  6. VALUES (3, 'Five Stars', 3)
  7. INSERT INTO post_comment (post_id, review, id)
  8. VALUES (4, 'A great reference book', 4)

纯 SQL 的 N+1 查询问题

如果您使用此 SQL 查询选择 post_comments:

  1. List comments = entityManager.createNativeQuery("""
  2. SELECT
  3. pc.id AS id,
  4. pc.review AS review,
  5. pc.post_id AS postId
  6. FROM post_comment pc
  7. """, Tuple.class)
  8. .getResultList();

然后,您决定为每个 post_comment 获取关联的 post title:

  1. for (Tuple comment : comments) {
  2. String review = (String) comment.get("review");
  3. Long postId = ((Number) comment.get("postId")).longValue();
  4. String postTitle = (String) entityManager.createNativeQuery("""
  5. SELECT
  6. p.title
  7. FROM post p
  8. WHERE p.id = :postId
  9. """)
  10. .setParameter("postId", postId)
  11. .getSingleResult();
  12. LOGGER.info(
  13. "The Post '{}' got this review '{}'",
  14. postTitle,
  15. review
  16. );
  17. }

您将触发 N+1 查询问题,因为您执行了 5 (1 + 4) 而不是一个 SQL 查询:

  1. SELECT
  2. pc.id AS id,
  3. pc.review AS review,
  4. pc.post_id AS postId
  5. FROM post_comment pc
  6. SELECT p.title FROM post p WHERE p.id = 1
  7. -- The Post 'High-Performance Java Persistence - Part 1' got this review
  8. -- 'Excellent book to understand Java Persistence'
  9. SELECT p.title FROM post p WHERE p.id = 2
  10. -- The Post 'High-Performance Java Persistence - Part 2' got this review
  11. -- 'Must-read for Java developers'
  12. SELECT p.title FROM post p WHERE p.id = 3
  13. -- The Post 'High-Performance Java Persistence - Part 3' got this review
  14. -- 'Five Stars'
  15. SELECT p.title FROM post p WHERE p.id = 4
  16. -- The Post 'High-Performance Java Persistence - Part 4' got this review
  17. -- 'A great reference book'

修复 N+1 查询问题非常容易。您需要做的就是提取原始 SQL 查询中所需的所有数据,如下所示:

  1. List comments = entityManager.createNativeQuery("""
  2. SELECT
  3. pc.id AS id,
  4. pc.review AS review,
  5. p.title AS postTitle
  6. FROM post_comment pc
  7. JOIN post p ON pc.post_id = p.id
  8. """, Tuple.class)
  9. .getResultList();
  10. for (Tuple comment : comments) {
  11. String review = (String) comment.get("review");
  12. String postTitle = (String) comment.get("postTitle");
  13. LOGGER.info(
  14. "The Post '{}' got this review '{}'",
  15. postTitle,
  16. review
  17. );
  18. }

这一次,只执行一个 SQL 查询来获取我们进一步感兴趣使用的所有数据。

JPA 和 Hibernate 的 N+1 查询问题

在使用 JPA 和 Hibernate 时,有几种方法可以触发 N+1 查询问题,因此了解如何避免这些情况非常重要。

对于下一个示例,假设我们将 post 和 post_comments 表映射到以下实体:

https://i.stack.imgur.com/rZJne.png

JPA 映射如下所示:

  1. @Entity(name = "Post")
  2. @Table(name = "post")
  3. public class Post {
  4. @Id
  5. private Long id;
  6. private String title;
  7. //Getters and setters omitted for brevity
  8. }
  9. @Entity(name = "PostComment")
  10. @Table(name = "post_comment")
  11. public class PostComment {
  12. @Id
  13. private Long id;
  14. @ManyToOne
  15. private Post post;
  16. private String review;
  17. //Getters and setters omitted for brevity
  18. }

FetchType.EAGER

对 JPA 关联隐式或显式使用 FetchType.EAGER 是一个坏主意,因为您将获取更多所需的数据。此外,FetchType.EAGER 策略也容易出现 N+1 查询问题。

不幸的是,@ManyToOne 和 @OneToOne 关联默认使用 FetchType.EAGER,因此如果您的映射如下所示:

  1. @ManyToOne
  2. private Post post;

您正在使用 FetchType.EAGER 策略,并且每次在使用 JPQL 或 Criteria API 查询加载某些 PostComment 实体时忘记使用 JOIN FETCH:

  1. List comments = entityManager
  2. .createQuery("""
  3. select pc
  4. from PostComment pc
  5. """, PostComment.class)
  6. .getResultList();

您将触发 N+1 查询问题:

  1. SELECT
  2. pc.id AS id1_1_,
  3. pc.post_id AS post_id3_1_,
  4. pc.review AS review2_1_
  5. FROM
  6. post_comment pc
  7. SELECT p.id AS id1_0_0_, p.title AS title2_0_0_ FROM post p WHERE p.id = 1
  8. SELECT p.id AS id1_0_0_, p.title AS title2_0_0_ FROM post p WHERE p.id = 2
  9. SELECT p.id AS id1_0_0_, p.title AS title2_0_0_ FROM post p WHERE p.id = 3
  10. SELECT p.id AS id1_0_0_, p.title AS title2_0_0_ FROM post p WHERE p.id = 4

请注意执行的附加 SELECT 语句,因为必须在返回 PostComment 实体的 List 之前获取 post 关联。

与调用 EntityManager 的 find 方法时使用的默认提取计划不同,JPQL 或 Criteria API 查询定义了一个显式计划,Hibernate 无法通过自动注入 JOIN FETCH 来更改该计划。因此,您需要手动执行此操作。

如果您根本不需要 post 关联,那么您在使用 FetchType.EAGER 时就不走运了,因为无法避免获取它。这就是为什么最好默认使用 FetchType.LAZY。

但是,如果你想使用 post 关联,那么你可以使用 JOIN FETCH 来避免 N+1 查询问题:

  1. List comments = entityManager.createQuery("""
  2. select pc
  3. from PostComment pc
  4. join fetch pc.post p
  5. """, PostComment.class)
  6. .getResultList();
  7. for(PostComment comment : comments) {
  8. LOGGER.info(
  9. "The Post '{}' got this review '{}'",
  10. comment.getPost().getTitle(),
  11. comment.getReview()
  12. );
  13. }

这一次,Hibernate 将执行一条 SQL 语句:

  1. SELECT
  2. pc.id as id1_1_0_,
  3. pc.post_id as post_id3_1_0_,
  4. pc.review as review2_1_0_,
  5. p.id as id1_0_1_,
  6. p.title as title2_0_1_
  7. FROM
  8. post_comment pc
  9. INNER JOIN
  10. post p ON pc.post_id = p.id
  11. -- The Post 'High-Performance Java Persistence - Part 1' got this review
  12. -- 'Excellent book to understand Java Persistence'
  13. -- The Post 'High-Performance Java Persistence - Part 2' got this review
  14. -- 'Must-read for Java developers'
  15. -- The Post 'High-Performance Java Persistence - Part 3' got this review
  16. -- 'Five Stars'
  17. -- The Post 'High-Performance Java Persistence - Part 4' got this review
  18. -- 'A great reference book'

FetchType.LAZY

即使您切换到对所有关联显式使用 FetchType.LAZY,您仍然会遇到 N+1 问题。

这一次,post 关联映射如下:

  1. @ManyToOne(fetch = FetchType.LAZY)
  2. private Post post;

现在,当您获取 PostComment 实体时:

  1. List comments = entityManager
  2. .createQuery("""
  3. select pc
  4. from PostComment pc
  5. """, PostComment.class)
  6. .getResultList();

Hibernate 将执行一条 SQL 语句:

  1. SELECT
  2. pc.id AS id1_1_,
  3. pc.post_id AS post_id3_1_,
  4. pc.review AS review2_1_
  5. FROM
  6. post_comment pc

但是,如果之后,您将引用延迟加载的 post 关联:

  1. for(PostComment comment : comments) {
  2. LOGGER.info(
  3. "The Post '{}' got this review '{}'",
  4. comment.getPost().getTitle(),
  5. comment.getReview()
  6. );
  7. }

您将收到 N+1 查询问题:

  1. SELECT p.id AS id1_0_0_, p.title AS title2_0_0_ FROM post p WHERE p.id = 1
  2. -- The Post 'High-Performance Java Persistence - Part 1' got this review
  3. -- 'Excellent book to understand Java Persistence'
  4. SELECT p.id AS id1_0_0_, p.title AS title2_0_0_ FROM post p WHERE p.id = 2
  5. -- The Post 'High-Performance Java Persistence - Part 2' got this review
  6. -- 'Must-read for Java developers'
  7. SELECT p.id AS id1_0_0_, p.title AS title2_0_0_ FROM post p WHERE p.id = 3
  8. -- The Post 'High-Performance Java Persistence - Part 3' got this review
  9. -- 'Five Stars'
  10. SELECT p.id AS id1_0_0_, p.title AS title2_0_0_ FROM post p WHERE p.id = 4
  11. -- The Post 'High-Performance Java Persistence - Part 4' got this review
  12. -- 'A great reference book'

由于 post 关联是延迟获取的,因此在访问延迟关联时将执行辅助 SQL 语句以构建日志消息。

同样,修复包括向 JPQL 查询添加 JOIN FETCH 子句:

  1. List comments = entityManager.createQuery("""
  2. select pc
  3. from PostComment pc
  4. join fetch pc.post p
  5. """, PostComment.class)
  6. .getResultList();
  7. for(PostComment comment : comments) {
  8. LOGGER.info(
  9. "The Post '{}' got this review '{}'",
  10. comment.getPost().getTitle(),
  11. comment.getReview()
  12. );
  13. }

而且,就像在 FetchType.EAGER 示例中一样,此 JPQL 查询将生成单个 SQL 语句。

即使您使用 FetchType.LAZY 并且不引用双向 @OneToOne JPA 关系的子关联,您仍然可以触发 N+1 查询问题。

如何自动检测 N+1 查询问题

如果您想在数据访问层中自动检测 N+1 查询问题,您可以使用 db-util 开源项目。

首先,您需要添加以下 Maven 依赖项:

  1. com.vladmihalcea
  2. db-util
  3. ${
  4. db-util.version}

之后,您只需使用 SQLStatementCountValidator 实用程序来断言生成的底层 SQL 语句:

  1. SQLStatementCountValidator.reset();
  2. List comments = entityManager.createQuery("""
  3. select pc
  4. from PostComment pc
  5. """, PostComment.class)
  6. .getResultList();
  7. SQLStatementCountValidator.assertSelectCount(1);

如果您使用 FetchType.EAGER 并运行上述测试用例,您将得到以下测试用例失败:

  1. SELECT
  2. pc.id as id1_1_,
  3. pc.post_id as post_id3_1_,
  4. pc.review as review2_1_
  5. FROM
  6. post_comment pc
  7. SELECT p.id as id1_0_0_, p.title as title2_0_0_ FROM post p WHERE p.id = 1
  8. SELECT p.id as id1_0_0_, p.title as title2_0_0_ FROM post p WHERE p.id = 2
  9. -- SQLStatementCountMismatchException: Expected 1 statement(s) but recorded 3 instead!

但是现在您遇到了分页问题。如果您有 10 辆汽车,每辆汽车有 4 个轮子,并且您想以每页 5 辆汽车对汽车进行分页。所以你基本上有SELECT cars, wheels FROM cars JOIN wheels LIMIT 0, 5。但是你得到的是 2 辆有 5 个轮子的汽车(第一辆有 4 个轮子的汽车,第二辆只有 1 个轮子的汽车),因为 LIMIT 会限制整个结果集,而不仅仅是根子句。

谢谢你的文章。我会读的。通过快速滚动 - 我看到解决方案是窗口函数,但它们在 MariaDB 中相当新 - 所以问题在旧版本中仍然存在。 :)

@VladMihalcea,每次您在解释 N+1 问题时提到 ManyToOne 案例时,我都会从您的文章或帖子中指出。但实际上人们最感兴趣的是与 N+1 问题有关的 OneToMany 案例。您能否参考并解释 OneToMany 案例?

@VladMicalcea 可以使用实体图代替 join fetch 吗?

感谢您的回答,它清除了一些东西。这本可以被接受的答案:)

答3:

保持自己快人一步,享受全网独家提供的一站式外包任务、远程工作、创意产品订阅服务–huntsbot.com

  1. SELECT
  2. table1.*
  3. , table2.*
  4. INNER JOIN table2 ON table2.SomeFkId = table1.SomeId

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

  1. SELECT table1.*
  2. SELECT table2.* WHERE SomeFkId = #

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

考虑:

  1. class House
  2. {
  3. int Id {
  4. get; set; }
  5. string Address {
  6. get; set; }
  7. Person[] Inhabitants {
  8. get; set; }
  9. }
  10. class Person
  11. {
  12. string Name {
  13. get; set; }
  14. int HouseId {
  15. get; set; }
  16. }

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

  1. Id Address Name HouseId
  2. 1 22 Valley St Dave 1
  3. 1 22 Valley St John 1
  4. 1 22 Valley St Mike 1

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

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

  1. Id Address
  2. 1 22 Valley St

使用单独的查询,例如

  1. SELECT * FROM Person WHERE HouseId = 1

并产生一个单独的数据集,如

  1. Name HouseId
  2. Dave 1
  3. John 1
  4. Mike 1

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

单选的优点是您可以预先获得所有数据,这可能是您最终想要的。 N+1 的优点是降低了查询复杂性,并且您可以使用延迟加载,其中子结果集仅在第一次请求时加载。

n + 1 的另一个优点是速度更快,因为数据库可以直接从索引返回结果。进行连接然后排序需要一个临时表,这比较慢。避免 n + 1 的唯一原因是,如果您与数据库交谈时有很多延迟。

加入和排序可以非常快(因为您将加入索引和可能排序的字段)。你的“n+1”有多大?你真的相信 n+1 问题只适用于高延迟的数据库连接吗?

@ariel - 你认为 N+1 是“最快”的建议是错误的,即使你的基准测试可能是正确的。这怎么可能?请参阅 en.wikipedia.org/wiki/Anecdotal_evidence,以及我在此问题的其他答案中的评论。

@Ariel - 我想我理解得很好:)。我只是想指出您的结果仅适用于一组条件。我可以很容易地构建一个相反的例子。那有意义吗?

重申一下,SELECT N + 1 问题的核心是:我有 600 条记录要检索。在一个查询中获得全部 600 个,还是在 600 个查询中一次获得 1 个更快。除非您使用的是 MyISAM 和/或您的规范化/索引模式不佳(在这种情况下 ORM 不是问题),否则经过适当调整的数据库将在 2 毫秒内返回 600 行,同时返回单个行每个大约 1 毫秒。所以我们经常看到 N + 1 需要数百毫秒,而连接只需要几个

答4:

保持自己快人一步,享受全网独家提供的一站式外包任务、远程工作、创意产品订阅服务–huntsbot.com

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

  1. ***** Table: Supplier *****
  2. +-----+-------------------+
  3. | ID | NAME |
  4. +-----+-------------------+
  5. | 1 | Supplier Name 1 |
  6. | 2 | Supplier Name 2 |
  7. | 3 | Supplier Name 3 |
  8. | 4 | Supplier Name 4 |
  9. +-----+-------------------+
  10. ***** Table: Product *****
  11. +-----+-----------+--------------------+-------+------------+
  12. | ID | NAME | DESCRIPTION | PRICE | SUPPLIERID |
  13. +-----+-----------+--------------------+-------+------------+
  14. |1 | Product 1 | Name for Product 1 | 2.0 | 1 |
  15. |2 | Product 2 | Name for Product 2 | 22.0 | 1 |
  16. |3 | Product 3 | Name for Product 3 | 30.0 | 2 |
  17. |4 | Product 4 | Name for Product 4 | 7.0 | 3 |
  18. +-----+-----------+--------------------+-------+------------+

因素:

供应商的惰性模式设置为“true”(默认)

用于查询 Product 的 Fetch 模式是 Select

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

缓存第一次没有发挥作用

供应商被访问

获取模式是选择获取(默认)

  1. // It takes Select fetch mode as a default
  2. Query query = session.createQuery( "from Product p");
  3. List list = query.list();
  4. // Supplier is being accessed
  5. displayProductsListWithSupplierName(results);
  6. select ... various field names ... from PRODUCT
  7. select ... various field names ... from SUPPLIER where SUPPLIER.id=?
  8. select ... various field names ... from SUPPLIER where SUPPLIER.id=?
  9. select ... various field names ... from SUPPLIER where SUPPLIER.id=?

结果:

1 选择产品声明

供应商的 N 条选择语句

这是 N+1 选择问题!

是否应该为供应商选择 1 次,然后为产品选择 N 次?

@bencampbell_ 是的,最初我也有同感。但是以他的例子,它是许多供应商的一种产品。

答5:

打造属于自己的副业,开启自由职业之旅,从huntsbot.com开始!

我不能直接评论其他答案,因为我没有足够的声誉。但值得注意的是,问题的出现本质上只是因为从历史上看,许多 dbms 在处理连接方面一直很差(MySQL 是一个特别值得注意的例子)。因此,n+1 通常比连接快得多。然后有一些方法可以改进 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 的糟糕性能而一直在避免连接,那么请在最新版本上重试。你可能会感到惊喜。

将 MySQL 的早期版本称为关系 DBMS 有点牵强……如果遇到这些问题的人使用的是真正的数据库,他们就不会遇到这些问题。 ;-)

有趣的是,随着 INNODB 引擎的引入和后续优化,MySQL 解决了许多此类问题,但您仍然会遇到试图推广 MYISAM 的人,因为他们认为它更快。

仅供参考,RDBMS 中使用的 3 种常见 JOIN 算法之一称为嵌套循环。它基本上是引擎盖下的 N+1 选择。唯一的区别是数据库做出了明智的选择,根据统计数据和索引使用它,而不是客户端代码明确地强制它走这条路。

@布兰登是的!就像 JOIN 提示和 INDEX 提示一样,在所有情况下强制执行某个执行路径很少会击败数据库。数据库几乎总是非常非常擅长选择获取数据的最佳方法。也许在 dbs 的早期,您需要以一种特殊的方式来“表达”您的问题以哄骗 db,但是经过数十年的世界级工程,您现在可以通过向您的数据库询问一个关系问题并让它获得最佳性能理清如何为您获取和组装这些数据。

不仅数据库使用索引和统计信息,所有操作也是本地 I/O,其中大部分操作通常针对高效缓存而不是磁盘。数据库程序员将大量精力投入到优化这些事情上。

答6:

huntsbot.com – 程序员副业首选,一站式外包任务、远程工作、创意产品分享订阅平台。

由于这个问题,我们离开了 Django 中的 ORM。基本上,如果你尝试去做

  1. for p in person:
  2. print p.car.colour

ORM 将愉快地返回所有人员(通常作为 Person 对象的实例),但随后它需要查询每个 Person 的 car 表。

一种简单且非常有效的方法是我称之为“折叠”的方法,它避免了来自关系数据库的查询结果应该映射回组成查询的原始表的荒谬想法。

第 1 步:广泛选择

  1. select * from people_car_colour; # this is a view or sql function

这将返回类似

  1. p.id | p.name | p.telno | car.id | car.type | car.colour
  2. -----+--------+---------+--------+----------+-----------
  3. 2 | jones | 2145 | 77 | ford | red
  4. 2 | jones | 2145 | 1012 | toyota | blue
  5. 16 | ashby | 124 | 99 | bmw | yellow

第 2 步:客观化

将结果吸入通用对象创建器,并在第三项之后拆分参数。这意味着“琼斯”对象不会被多次制作。

第 3 步:渲染

  1. for p in people:
  2. print p.car.colour # no more car queries

有关 python 的 fanfolding 实现,请参阅 this web page。

我很高兴我偶然发现了你的帖子,因为我以为我快疯了。当我发现 N+1 问题时,我的直接想法是——好吧,你为什么不创建一个包含你需要的所有信息的视图,然后从该视图中提取?你证实了我的立场。谢谢你,先生。

因为这个问题,我们离开了 Django 中的 ORM。 嗯? Django 有 select_related,它旨在解决这个问题 - 事实上,它的文档以类似于您的 p.car.colour 示例的示例开头。

这是一个老答案,我们现在在 Django 中有 select_related() 和 prefetch_related()。

凉爽的。但是 select_related() 和朋友似乎没有对诸如 LEFT OUTER JOIN 的连接进行任何明显有用的推断。问题不是接口问题,而是与对象和关系数据是可映射的奇怪想法有关的问题......在我看来。

答7:

huntsbot.com全球7大洲远程工作机会,探索不一样的工作方式

Here’s a good description of the problem

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

答8:

huntsbot.com – 程序员副业首选,一站式外包任务、远程工作、创意产品分享订阅平台。

假设您有 COMPANY 和 EMPLOYEE。 COMPANY 有许多 EMPLOYEES(即 EMPLOYEE 有一个字段 COMPANY_ID)。

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

这就是 EJB 实体 Bean 的初始版本的工作方式。我相信像 Hibernate 这样的东西已经消除了这一点,但我不太确定。大多数工具通常包含有关其映射策略的信息。

答9:

打造属于自己的副业,开启自由职业之旅,从huntsbot.com开始!

查看 Ayende 关于主题的帖子:Combating the Select N + 1 Problem In NHibernate。

基本上,当使用像 NHibernate 或 EntityFramework 这样的 ORM 时,如果您有一对多(主从)关系,并且想要列出每个主记录的所有详细信息,您必须对数据库,“N”是主记录的数量:1 次查询获取所有主记录,N 次查询,每个主记录一个,获取每个主记录的所有详细信息。

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

但是,ORM 有一些选项可以避免这个问题,主要是使用 JOIN。

连接不是一个好的解决方案(通常),因为它们可能会产生笛卡尔积,这意味着结果行数是根表结果数乘以每个子表中的结果数。在多个等级制度层面上尤其糟糕。选择 20 个“博客”,每个博客有 100 个“帖子”,每个帖子有 10 个“评论”,将产生 20000 个结果行。 NHibernate 有一些变通方法,比如“batch-size”(在父 id 上使用 in 子句选择子项)或“subselect”。

答10:

huntsbot.com聚合了超过10+全球外包任务平台的外包需求,寻找外包任务与机会变的简单与高效。

发出 1 个返回 100 个结果的查询比发出 100 个每个返回 1 个结果的查询要快得多。

答11:

HuntsBot周刊–不定时分享成功产品案例,学习他们如何成功建立自己的副业–huntsbot.com

在我看来,Hibernate Pitfall: Why Relationships Should Be Lazy中写的文章与真正的 N+1 问题完全相反。

如果您需要正确的解释,请参考Hibernate - Chapter 19: Improving Performance - Fetching Strategies

Select fetching(默认)极易受到 N+1 选择问题的影响,因此我们可能希望启用 join fetching

我读了休眠页面。它并没有说 N+1 选择问题实际上是什么。但它说你可以使用连接来修复它。

选择提取需要批量大小,以便在一个选择语句中选择多个父对象的子对象。子选择可能是另一种选择。如果您有多个层次结构级别并且创建了笛卡尔积,则连接可能会变得非常糟糕。

原文链接:https://www.huntsbot.com/qa/maL7/what-is-the-n1-selects-problem-in-orm-object-relational-mapping?lang=zh_CN&from=csdn

与HuntsBot一起,探索全球自由职业机会–huntsbot.com

发表评论

表情:
评论列表 (有 0 条评论,229人围观)

还没有评论,来说两句吧...

相关阅读

    相关 ORMN+1问题

    ORM能够让事情变得简单,也会让有些事情变得复杂。有人说,这不就是一个SQL语句的事嘛,干嘛在ORM里面就这么复杂。 一、什么是N+1问题 我们来解释什么是N+1的问题

    相关 ORM(对象关系映射)

    一、作用 用于实现面向对象编程语言里不同类型系统的数据之间的转换,换言之,就是用面向对象的方式去操作数据库的创建表以及增删改查等操作 二、优缺点 优点: 1.OR