`
pengchua
  • 浏览: 152683 次
  • 性别: Icon_minigender_1
  • 来自: 上海
文章分类
社区版块
存档分类
最新评论

n+1次select查询问题

阅读更多

方法一:<o:p></o:p>

Session的缓存中存放的是相互关联的对象图。默认情况下,当Hibernate从数据库中加载Customer对象时,会同时加载所有关联的 Order对象。以CustomerOrder类为例,假定ORDERS表的CUSTOMER_ID外键允许为null,图1列出了CUSTOMERS 表和ORDERS表中的记录。

<v:shapetype id="_x0000_t75" coordsize="21600,21600" o:spt="75" o:preferrelative="t" path="m@4@5l@4@11@9@11@9@5xe" filled="f" stroked="f"><v:stroke joinstyle="miter"></v:stroke><v:formulas><v:f eqn="if lineDrawn pixelLineWidth 0"></v:f><v:f eqn="sum @0 1 0"></v:f><v:f eqn="sum 0 0 @1"></v:f><v:f eqn="prod @2 1 2"></v:f><v:f eqn="prod @3 21600 pixelWidth"></v:f><v:f eqn="prod @3 21600 pixelHeight"></v:f><v:f eqn="sum @0 0 1"></v:f><v:f eqn="prod @6 1 2"></v:f><v:f eqn="prod @7 21600 pixelWidth"></v:f><v:f eqn="sum @8 21600 0"></v:f><v:f eqn="prod @7 21600 pixelHeight"></v:f><v:f eqn="sum @10 21600 0"></v:f></v:formulas><v:path o:extrusionok="f" gradientshapeok="t" o:connecttype="rect"></v:path><o:lock v:ext="edit" aspectratio="t"></o:lock></v:shapetype><v:shape id="_x0000_i1025" style="WIDTH: 453pt; HEIGHT: 187.5pt" type="#_x0000_t75" alt="" o:button="t"><v:imagedata src="file:///C:\DOCUME~1\pengch\LOCALS~1\Temp\msohtml1\01\clip_image001.gif" o:href="http://www.javathinker.org/image/hibernate_essay_8_1.gif"></v:imagedata></v:shape>

以下Sessionfind()方法用于到数据库中检索所有的Customer对象:

List customerLists=session.find("from Customer as c");

运行以上find()方法时,Hibernate将先查询CUSTOMERS表中所有的记录,然后根据每条记录的ID,到ORDERS表中查询有参照关系的记录,Hibernate将依次执行以下select语句:

select * from CUSTOMERS;
select * from ORDERS where CUSTOMER_ID=1;
select * from ORDERS where CUSTOMER_ID=2;
select * from ORDERS where CUSTOMER_ID=3;
select * from ORDERS where CUSTOMER_ID=4;

通过以上5select语句,Hibernate最后加载了4Customer对象和5Order对象,在内存中形成了一幅关联的对象图,参见图2

<v:shape id="_x0000_i1026" style="WIDTH: 287.25pt; HEIGHT: 206.25pt" type="#_x0000_t75" alt="" o:button="t"><v:imagedata src="file:///C:\DOCUME~1\pengch\LOCALS~1\Temp\msohtml1\01\clip_image002.gif" o:href="http://www.javathinker.org/image/hibernate_essay_8_2.gif"></v:imagedata></v:shape>

Hibernate
在检索与Customer关联的Order对象时,使用了默认的立即检索策略。这种检索策略存在两大不足:

1 select语句的数目太多,需要频繁的访问数据库,会影响检索性能。如果需要查询nCustomer对象,那么必须执行n+1select查询语句。这就是经典的n+1select查询问题。这种检索策略没有利用SQL的连接查询功能,例如以上5select语句完全可以通过以下1select语句来完成:

select * from CUSTOMERS left outer join ORDERS
on CUSTOMERS.ID=ORDERS.CUSTOMER_ID

以上select语句使用了SQL的左外连接查询功能,能够在一条select语句中查询出CUSTOMERS表的所有记录,以及匹配的ORDERS表的记录。

2)在应用逻辑只需要访问Customer对象,而不需要访问Order对象的场合,加载Order对象完全是多余的操作,这些多余的Order对象白白浪费了许多内存空间。
为了解决以上问题,Hibernate提供了其他两种检索策略:延迟检索策略和迫切左外连接检索策略。延迟检索策略能避免多余加载应用程序不需要访问的关联对象,迫切左外连接检索策略则充分利用了SQL的外连接查询功能,能够减少select语句的数目。

<o:p> </o:p>

<o:p> </o:p>

其实这个问题在Hibernate in Action中已经有很多种解决办法了。但我觉得其中最好的办法是用CriteriaFetchMode来解决,但是Hibernate in Action中写的很不详细。我昨晚试了好长时间来的到答案。下面总结一下。
需求这样的,我有四张表(one,two,three,four)从one一直外键关联到four。结构如下
<v:shape id="_x0000_i1027" style="WIDTH: 376.5pt; HEIGHT: 222pt" type="#_x0000_t75" alt="hibernatefetch.jpg"><v:imagedata src="file:///C:\DOCUME~1\pengch\LOCALS~1\Temp\msohtml1\01\clip_image003.jpg" o:href="http://www.blogjava.net/images/blogjava_net/mstar/pics20050930/hibernatefetch.jpg"></v:imagedata></v:shape>
现在在Session中得到One,并从One里一直取到Four里的内容。如果简单的用Session.get来实现是这样的。<o:p></o:p>

One one = (One)session.get(One.class,new Integer(1));
        Iterator iterone = one.getTwos().iterator();
        
while(iterone.hasNext()){
            Two two = (Two) iterone.next();
            Iterator itertwo = two.getThrees().iterator();
            
while(itertwo.hasNext()){
                Three three = (Three) itertwo.next();
                three.getFours().size();                
            }
        }
<o:p></o:p>

这样我在Session关闭后返回的One里是从OneFour的信息都有的。
然而这样做所导致的结果是生成大量的SQL查询,这是一个典型的n+1 Selects问题。如果系统结构层次多,符合条件的记录多,那么Hibernate为你生成的SQL查询将是难以接受的。
对于这个例子生成的SQL是这样的
Hibernate: select one0_.c_one_id as c1_0_, one0_.c_one_text as c2_3_0_ from One one0_ where one0_.c_one_id=?
Hibernate: select twos0_.c_one_id as c2_1_, twos0_.c_two_id as c1_1_, twos0_.c_two_id as c1_0_, twos0_.c_one_id as c2_2_0_, twos0_.c_two_text as c3_2_0_ from Two twos0_ where twos0_.c_one_id=?
Hibernate: select threes0_.c_two_id as c2_1_, threes0_.c_three_id as c1_1_, threes0_.c_three_id as c1_0_, threes0_.c_two_id as c2_1_0_, threes0_.c_three_text as c3_1_0_ from Three threes0_ where threes0_.c_two_id=?
Hibernate: select fours0_.c_three_id as c2_1_, fours0_.c_four_id as c1_1_, fours0_.c_four_id as c1_0_, fours0_.c_three_id as c2_0_0_, fours0_.c_four_text as c3_0_0_ from Four fours0_ where fours0_.c_three_id=?
Hibernate: select fours0_.c_three_id as c2_1_, fours0_.c_four_id as c1_1_, fours0_.c_four_id as c1_0_, fours0_.c_three_id as c2_0_0_, fours0_.c_four_text as c3_0_0_ from Four fours0_ where fours0_.c_three_id=?
Hibernate: select threes0_.c_two_id as c2_1_, threes0_.c_three_id as c1_1_, threes0_.c_three_id as c1_0_, threes0_.c_two_id as c2_1_0_, threes0_.c_three_text as c3_1_0_ from Three threes0_ where threes0_.c_two_id=?
Hibernate: select fours0_.c_three_id as c2_1_, fours0_.c_four_id as c1_1_, fours0_.c_four_id as c1_0_, fours0_.c_three_id as c2_0_0_, fours0_.c_four_text as c3_0_0_ from Four fours0_ where fours0_.c_three_id=?
Hibernate: select fours0_.c_three_id as c2_1_, fours0_.c_four_id as c1_1_, fours0_.c_four_id as c1_0_, fours0_.c_three_id as c2_0_0_, fours0_.c_four_text as c3_0_0_ from Four fours0_ where fours0_.c_three_id=?
对于这样的问题,在没有Hibernate以前我们一般都用jdbc来做,那样的话我们其实用一个进行3joinsql语句就可以实现,但是这样解决也有问题,就是返回的ResultSet中的数据非常多,而且杂乱,其实是从onefour平行排列的。对于这样的结果集我们要把它手动影射曾对象结构也是一个很复杂的操作。
幸好Hibernate3可以为我们做这些事情(我再一次被Hibernate的强大所震撼)
上面的实现可以用Criteria来实现:<o:p></o:p>

session = sessionFactory.openSession();
        Criteria criteria = session.createCriteria(One.
class);
        criteria.add(Expression.eq("COneId",
new Integer(1)));
        one = (One)criteria.setFetchMode("twos",FetchMode.JOIN).setFetchMode("twos.threes",FetchMode.JOIN).setFetchMode("twos.threes.fours",FetchMode.JOIN).uniqueResult();
        session.close();
<o:p></o:p>

这里的重点是这句话criteria.setFetchMode("twos",FetchMode.JOIN).setFetchMode("twos.threes",FetchMode.JOIN).setFetchMode("twos.threes.fours",FetchMode.JOIN).uniqueResult();
在用Criteria之前先设置FetchMode,应为Criteria是动态生成sql语句的,所以生成的sql就是一层层Join下去的。
setFetchMode
StringMode)第一个参数是association path,用"."来表示路径。这一点具体的例子很少,文档也没有写清楚。我也是试了很久才试出来的。
就这个例子来所把因为取道第四层,所以要进行三次setFetchMode
第一次的路径是twos,一位one中有twoSet。这个具体要更具hbm.xml的配置来定。
第二个路径就是twos.threes
第三个就是twos.threes.fours
一次类推,一层层增加的。
这样做法最终生成的SQL是这样的:
Hibernate: select this_.c_one_id as c1_3_, this_.c_one_text as c2_3_3_, twos2_.c_one_id as c2_5_, twos2_.c_two_id as c1_5_, twos2_.c_two_id as c1_0_, twos2_.c_one_id as c2_2_0_, twos2_.c_two_text as c3_2_0_, threes3_.c_two_id as c2_6_, threes3_.c_three_id as c1_6_, threes3_.c_three_id as c1_1_, threes3_.c_two_id as c2_1_1_, threes3_.c_three_text as c3_1_1_, fours4_.c_three_id as c2_7_, fours4_.c_four_id as c1_7_, fours4_.c_four_id as c1_2_, fours4_.c_three_id as c2_0_2_, fours4_.c_four_text as c3_0_2_ from One this_ left outer join Two twos2_ on this_.c_one_id=twos2_.c_one_id left outer join Three threes3_ on twos2_.c_two_id=threes3_.c_two_id left outer join Four fours4_ on threes3_.c_three_id=fours4_.c_three_id where this_.c_one_id=?
虽然很长但是只有一条SQL语句。性能要好很多。Hibernate的强大之处是它会把返回的ResultSet自动影射到你的对象模型里面去。这就为我们省了很多事。

看来Hibernate真是一个耐人寻味的Framework啊。<o:p></o:p>

"from A j left outer join j.Bs a where  ......

可以直接进行左连接:一对多的关系。后面不能再写on 。。。这个句子会自动生成sql,其中会有的。<o:p></o:p>

" and a.id is not null");   判断不为空。<o:p></o:p>

方法二:<o:p></o:p>

还有一种解决方法:就是直接写成一个视图用来作查询,这样外连接都已经写好了。<o:p></o:p>

方法三:<o:p></o:p>

对于双向的一对多的关系lazy <o:p></o:p>

一方:<set name="processVOs" <o:p></o:p>

        inverse="true" <o:p></o:p>

        cascade="all-delete-orphan" <o:p></o:p>

        lazy="true" <o:p></o:p>

        ><o:p></o:p>

多方:<class
   
name="ProcessVO"
   
table="TABLE_BPROCESS "
   
lazy="true"   //一定要为true<o:p></o:p>

>
前且在对应的VO中不能有set.sixe()等大小。这样因为延时加载,如果取不出数据所以会发生错误。应该改为lazy=”false”<o:p></o:p>

如果有这样的情况:处理方法1.采用视图;2。采用左连接 (也就是上面的方法一) 如: select id, count(f_id) from A left outer join B  where ……   group by id  order by…..<o:p></o:p>

                                                              <o:p></o:p>

<o:p> </o:p>

<o:p> </o:p>

<o:p> </o:p>

<o:p> </o:p>

<o:p> </o:p>

<o:p> </o:p>

<o:p> </o:p>

<o:p> </o:p>

<o:p> </o:p>

<o:p> </o:p>

<o:p> </o:p>

 

分享到:
评论

相关推荐

    hibernate的n+1问题.docx

    然而,使用不当可能会导致性能瓶颈,其中最典型的就是“N+1次SELECT查询问题”。本文将深入探讨这个问题,以及如何通过优化检索策略来解决它。 N+1次SELECT查询问题源于Hibernate的默认行为。当从数据库中加载一个...

    ibatis解决多对一n+1问题(更新上传例子(mybatis)代码)

    "ibatis解决多对一n+1问题"这个主题聚焦于MyBatis框架中如何高效地处理多对一关联查询,避免出现性能瓶颈的“n+1”问题。这个问题通常发生在查询一对多关系时,如果不对查询进行优化,会导致大量的额外数据库访问,...

    ibatis N+1问题

    2. **连接查询(JOIN)**: 通过在SQL语句中使用JOIN操作,将原本的多次查询合并为一次,从而避免N+1问题。不过,这种方法需要注意的是,当数据量过大时,可能会导致内存压力增大。 3. **延迟加载(Lazy Loading)**...

    djangoORM如何处理N+1查询.pdf

    标题中的“django ORM如何处理N+1查询”指的是在使用Django的Object-Relational Mapping (ORM)系统时,如何避免或解决常见的N+1查询问题。N+1查询问题通常发生在试图从关联模型中获取数据时,导致数据库执行过多的...

    ibatis n+1选择问题 的几种解决方案

    N+1问题发生在当我们执行一系列单独的SQL查询来获取关联数据,而不是一次性加载所有所需数据。这可能导致大量的数据库交互,从而降低系统性能。下面将详细介绍几种解决iBATIS中的N+1选择问题的方法。 1. **批处理...

    Mysql解决数据库N+1查询问题

    这个问题出现在当我们需要获取一个对象及其关联对象时,通常会先执行一次主查询获取主对象,然后对每个主对象再单独执行一次查询来获取其关联对象,导致查询次数为N+1,其中N为主对象的数量。在高并发或大数据量的...

    djangoORM如何处理N+1查询.docx

    然而,如同其他ORM工具,Django ORM也可能导致一种性能问题,通常被称为“N+1查询”问题,或者更准确地说,“1+N查询问题”。 1+N查询问题指的是在进行数据检索时,对于一个主对象,我们需要额外单独查询多个关联...

    Ruby On Rails中如何避免N+1问题

    N+1问题 N+1问题是数据库访问中最常见的一个性能问题,首先介绍一下什么是N+1问题: 举个例子,我们数据库中有两张表,一个是Customers,一个是Orders。...这样我们实际对数据库做了N+1次查询:选择所有Customer

    浅谈Hibernate n+1问题

    2. 使用二级缓存,在对象更新、删除、添加相对于查询要少得多时,二级缓存的应用将不怕 n+1 问题,因为即使第一次查询很慢,之后直接缓存命中也是很快的。 3. 设定 fetch=join(annotation : @ManyToOne() @Fetch...

    Rails N + 1查询自动检测为零误报/误报-Ruby开发

    已检测到N + 1个查询:从`users`的WHERE`users.`id` = 20 LIMIT 1中选择SELECT`users`。*从`users` WHERE`users`.`id` = 21 LIMIT 1从`users` WHERE`users``.id`中选择``users..id''= 22 LIMIT 1 SELECT`users .. *...

    n-plus-one:Prisma如何解决N + 1问题-2020年Prisma日

    Prisma如何解决N + 1问题-2020年Prisma日 该存储库包含2020年Prisma Day演讲“ Prisma如何解决N + 1问题”的相应代码。 结果 名称 要求/秒 字节/序列 阿波罗PG 2.6 625 kB 阿波罗棱镜发现 7.8 1.87兆字节 ...

    Hibernate 1+N问题详解

    - 但同时也可能导致“N+1”问题,即除了主表查询外,还需要额外的查询来加载每个关联的子记录,这在子记录较多时可能会导致性能下降。 **2. FetchType.LAZY:** - `LAZY`加载方式则是延迟加载,即只有当真正访问到...

    MVC+EF+LINQ分页加查询.zip

    5. **查询**:在描述中提到了查询,这可能包括了基本的SELECT语句以及更复杂的联接、分组、排序等操作。使用LINQ,开发者可以方便地编写这些查询,然后由EF自动转换为相应的SQL语句。 6. **存储过程**:存储过程是...

    graphql-query-resolver:最小化由GraphQL和ActiveRecord生成的N + 1个查询

    GraphQL :: QueryResolver GraphQL :: QueryResolver是一个附加,它可使您的字段解析器将ActiveRecord发出的N + 1个SELECT最小化。 GraphQL :: QueryResolver将分析传入的GraphQL查询中的AST,并尝试将查询选择与...

    prosopite:Rails N + 1查询自动检测为零误报误报

    N+1 queries detected: SELECT `users`.* FROM `users` WHERE `users`.`id` = 20 LIMIT 1 SELECT `users`.* FROM `users` WHERE `users`.`id` = 21 LIMIT 1 SELECT `users`.* FROM `users` WHERE `users`.`id` =...

    goone:goone在go中找到N + 1个查询

    Goone goone在go中找到N + 1(严格来说是for循环中的调用SQL)查询例子package mainimport ("database/sql""fmt""log"_ "github.com/go-sql-driver/mysql")type Person struct {Name stringJobID int}type Job ...

    Select 语句的基本使用

    使用 TOP n [PERCENT]选项限制返回的数据行数,TOP n 说明返回 n 行,而 TOP n PERCENT 时,说明 n 是表示百分数,指定返回的行数等于总行数的百分之几。例如: SELECT TOP 2 * FROM testtable SELECT TOP 20 ...

    MyBatis之自查询使用递归实现 N级联动效果(两种实现方式)

    int[] nums = new int[n+1]; nums[0]=1; nums[1]=1; for (int i =2;i&lt;n;i++){ nums[i] = nums[i-2]+nums[i-1]; } return nums[n-1]; } ``` 在上面的代码中,我们使用数组来存储斐波那契数列的每一项值,然后...

Global site tag (gtag.js) - Google Analytics